@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,112 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { type ThreeElements } from '@octanejs/three';
3
+ import { useCallback, useEffect, useState } from '@octanejs/three/renderer';
4
+ import type { OctaneNode } from 'octane';
5
+ import { AccumulativeShadows, type AccumulativeShadowsProps, RandomizedLight, type RandomizedLightProps } from './AccumulativeShadows.three.tsrx';
6
+ import { Bounds, useBounds } from './Bounds.three.tsrx';
7
+ import { Center, type CenterProps, type OnCenterCallbackProps } from './Center.three.tsrx';
8
+ import { ContactShadows, type ContactShadowsProps } from './ContactShadows.three.tsrx';
9
+ import { Environment, type EnvironmentProps } from './Environment.three.tsrx';
10
+
11
+ const presets = {
12
+ rembrandt: { main: [1, 2, 1], fill: [-2, -0.5, -2] },
13
+ portrait: { main: [-1, 2, 0.5], fill: [-1, 0.5, -1.5] },
14
+ upfront: { main: [0, 2, 1], fill: [-1, 0.5, -1.5] },
15
+ soft: { main: [-2, 4, 4], fill: [-1, 0.5, -1.5] },
16
+ } satisfies Record<string, { main: [number, number, number]; fill: [number, number, number] }>;
17
+
18
+ type StageShadows = Partial<AccumulativeShadowsProps> & Partial<RandomizedLightProps> & Partial<ContactShadowsProps> & {
19
+ type: 'contact' | 'accumulative';
20
+ offset?: number;
21
+ bias?: number;
22
+ normalBias?: number;
23
+ size?: number;
24
+ };
25
+
26
+ export type StageProps = Omit<ThreeElements['group'], 'ref'> & {
27
+ preset?: keyof typeof presets | { main: [number, number, number]; fill: [number, number, number] };
28
+ shadows?: boolean | 'contact' | 'accumulative' | StageShadows;
29
+ adjustCamera?: boolean | number;
30
+ environment?: NonNullable<EnvironmentProps['preset']> | Partial<EnvironmentProps> | null;
31
+ intensity?: number;
32
+ center?: Partial<CenterProps>;
33
+ children?: OctaneNode;
34
+ };
35
+
36
+ function Refit({ radius, adjustCamera }: { radius: number; adjustCamera: boolean | number }) @{
37
+ const api = useBounds();
38
+ useEffect(() => {
39
+ if (adjustCamera) api.refresh().clip().fit();
40
+ }, [radius, adjustCamera]);
41
+ null
42
+ }
43
+
44
+ export function Stage({
45
+ children,
46
+ center,
47
+ adjustCamera = true,
48
+ intensity = 0.5,
49
+ shadows = 'contact',
50
+ environment = 'city',
51
+ preset = 'rembrandt',
52
+ ...props
53
+ }: StageProps) @{
54
+ const config = typeof preset === 'string' ? presets[preset] : preset;
55
+ const [dimensions, setDimensions] = useState({ radius: 0, width: 0, height: 0, depth: 0 });
56
+ const { radius, height } = dimensions;
57
+ const shadowConfig = typeof shadows === 'object' ? shadows : undefined;
58
+ const shadowBias = shadowConfig?.bias ?? -0.0001;
59
+ const normalBias = shadowConfig?.normalBias ?? 0;
60
+ const shadowSize = shadowConfig?.size ?? 1024;
61
+ const shadowOffset = shadowConfig?.offset ?? 0;
62
+ const contactShadow = shadows === 'contact' || shadowConfig?.type === 'contact';
63
+ const accumulativeShadow = shadows === 'accumulative' || shadowConfig?.type === 'accumulative';
64
+ const shadowSpread = shadowConfig ?? {};
65
+ const environmentProps = !environment ? null : typeof environment === 'string' ? { preset: environment } : environment;
66
+ const onCentered = useCallback((centered: OnCenterCallbackProps) => {
67
+ const { width, height: nextHeight, depth, boundingSphere } = centered;
68
+ setDimensions({ radius: boundingSphere.radius, width, height: nextHeight, depth });
69
+ center?.onCentered?.(centered);
70
+ }, []);
71
+ <>
72
+ <ambientLight intensity={intensity / 3} />
73
+ <spotLight
74
+ penumbra={1}
75
+ position={[config.main[0] * radius, config.main[1] * radius, config.main[2] * radius]}
76
+ intensity={intensity * 2}
77
+ castShadow={Boolean(shadows)}
78
+ shadow-bias={shadowBias}
79
+ shadow-normalBias={normalBias}
80
+ shadow-mapSize={shadowSize}
81
+ />
82
+ <pointLight position={[config.fill[0] * radius, config.fill[1] * radius, config.fill[2] * radius]} intensity={intensity} />
83
+ <Bounds fit={Boolean(adjustCamera)} clip={Boolean(adjustCamera)} margin={Number(adjustCamera)} observe {...props}>
84
+ <Refit radius={radius} adjustCamera={adjustCamera} />
85
+ <Center {...center} position={[0, shadowOffset / 2, 0]} onCentered={onCentered}>
86
+ {children}
87
+ </Center>
88
+ </Bounds>
89
+ <group position={[0, -height / 2 - shadowOffset / 2, 0]} name="stage-shadows">
90
+ @if (contactShadow) {
91
+ <ContactShadows scale={radius * 4} far={radius} blur={2} {...shadowSpread as ContactShadowsProps} />
92
+ }
93
+ @if (accumulativeShadow) {
94
+ <AccumulativeShadows temporal frames={100} alphaTest={0.9} toneMapped scale={radius * 4} {...shadowSpread as AccumulativeShadowsProps}>
95
+ <RandomizedLight
96
+ amount={(shadowSpread as RandomizedLightProps).amount ?? 8}
97
+ radius={(shadowSpread as RandomizedLightProps).radius ?? radius}
98
+ ambient={(shadowSpread as RandomizedLightProps).ambient ?? 0.5}
99
+ intensity={(shadowSpread as RandomizedLightProps).intensity ?? 1}
100
+ position={[config.main[0] * radius, config.main[1] * radius, config.main[2] * radius]}
101
+ size={radius * 4}
102
+ bias={-shadowBias}
103
+ mapSize={shadowSize}
104
+ />
105
+ </AccumulativeShadows>
106
+ }
107
+ </group>
108
+ @if (environmentProps) {
109
+ <Environment {...environmentProps} />
110
+ }
111
+ </>
112
+ }
@@ -0,0 +1,103 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { type ThreeRef, useFrame } from '@octanejs/three';
3
+ import { useMemo, useState } from '@octanejs/three/renderer';
4
+ import * as THREE from 'three';
5
+
6
+ export type StarsProps = {
7
+ radius?: number;
8
+ depth?: number;
9
+ count?: number;
10
+ factor?: number;
11
+ saturation?: number;
12
+ fade?: boolean;
13
+ speed?: number;
14
+ };
15
+
16
+ export class StarfieldMaterial extends THREE.ShaderMaterial {
17
+ constructor() {
18
+ super({
19
+ uniforms: { time: { value: 0 }, fade: { value: 1 } },
20
+ vertexShader: `uniform float time;
21
+ attribute float size;
22
+ varying vec3 vColor;
23
+ void main() {
24
+ vColor = color;
25
+ vec4 mvPosition = modelViewMatrix * vec4(position, 0.5);
26
+ gl_PointSize = size * (30.0 / -mvPosition.z) * (3.0 + sin(time + 100.0));
27
+ gl_Position = projectionMatrix * mvPosition;
28
+ }`,
29
+ fragmentShader: `uniform sampler2D pointTexture;
30
+ uniform float fade;
31
+ varying vec3 vColor;
32
+ void main() {
33
+ float opacity = 1.0;
34
+ if (fade == 1.0) {
35
+ float d = distance(gl_PointCoord, vec2(0.5, 0.5));
36
+ opacity = 1.0 / (1.0 + exp(16.0 * (d - 0.25)));
37
+ }
38
+ gl_FragColor = vec4(vColor, opacity);
39
+ #include <tonemapping_fragment>
40
+ #include <colorspace_fragment>
41
+ }`,
42
+ });
43
+ }
44
+ }
45
+
46
+ function genStar(radius: number): THREE.Vector3 {
47
+ return new THREE.Vector3().setFromSpherical(
48
+ new THREE.Spherical(
49
+ radius,
50
+ Math.acos(1 - Math.random() * 2),
51
+ Math.random() * 2 * Math.PI,
52
+ ),
53
+ );
54
+ }
55
+
56
+ function assignRef<T>(ref: ThreeRef<T> | undefined, value: T | null): void {
57
+ if (Array.isArray(ref)) for (const item of ref) assignRef(item, value);
58
+ else if (typeof ref === 'function') ref(value);
59
+ else if (ref && 'current' in ref) ref.current = value;
60
+ }
61
+
62
+ export function Stars({
63
+ radius = 100,
64
+ depth = 50,
65
+ count = 5000,
66
+ saturation = 0,
67
+ factor = 4,
68
+ fade = false,
69
+ speed = 1,
70
+ ref,
71
+ }: StarsProps & { ref?: ThreeRef<THREE.Points> }) @{
72
+ const [position, color, size] = useMemo(() => {
73
+ const positions: number[] = [];
74
+ const colors: number[] = [];
75
+ const sizes = Array.from({ length: count }, () => (0.5 + 0.5 * Math.random()) * factor);
76
+ const currentColor = new THREE.Color();
77
+ let currentRadius = radius + depth;
78
+ const increment = depth / count;
79
+ for (let index = 0; index < count; index++) {
80
+ currentRadius -= increment * Math.random();
81
+ positions.push(...genStar(currentRadius).toArray());
82
+ currentColor.setHSL(index / count, saturation, 0.9);
83
+ colors.push(currentColor.r, currentColor.g, currentColor.b);
84
+ }
85
+ return [new Float32Array(positions), new Float32Array(colors), new Float32Array(sizes)];
86
+ }, [count, depth, factor, radius, saturation]);
87
+ const [material] = useState(() => new StarfieldMaterial());
88
+ material.blending = THREE.AdditiveBlending;
89
+ material.uniforms.fade.value = fade;
90
+ material.depthWrite = false;
91
+ material.transparent = true;
92
+ material.vertexColors = true;
93
+ useFrame((state) => (material.uniforms.time.value = state.clock.elapsedTime * speed));
94
+ const mergedRef = useMemo(() => (value: THREE.Points | null) => assignRef(ref, value), [ref]);
95
+ <points ref={mergedRef}>
96
+ <bufferGeometry>
97
+ <bufferAttribute attach="attributes-position" args={[position, 3]} />
98
+ <bufferAttribute attach="attributes-color" args={[color, 3]} />
99
+ <bufferAttribute attach="attributes-size" args={[size, 1]} />
100
+ </bufferGeometry>
101
+ <primitive object={material} attach="material" />
102
+ </points>
103
+ }
@@ -0,0 +1,35 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { addAfterEffect, addEffect } from '@octanejs/three';
3
+ import { useEffect, useLayoutEffect, useState } from '@octanejs/three/renderer';
4
+ import StatsImpl from 'stats.js';
5
+
6
+ export type StatsProps = {
7
+ showPanel?: number;
8
+ className?: string;
9
+ parent?: { current: HTMLElement | null };
10
+ };
11
+
12
+ export function Stats({ showPanel = 0, className, parent }: StatsProps) @{
13
+ const [stats, setStats] = useState<StatsImpl | null>(null);
14
+ useLayoutEffect(() => {
15
+ setStats(new StatsImpl());
16
+ }, []);
17
+ useEffect(() => {
18
+ if (stats) {
19
+ const node = (parent && parent.current) || document.body;
20
+ stats.showPanel(showPanel);
21
+ node?.appendChild(stats.dom);
22
+ const classNames = (className ?? '').split(' ').filter((value) => value);
23
+ if (classNames.length) stats.dom.classList.add(...classNames);
24
+ const begin = addEffect(() => stats.begin());
25
+ const end = addAfterEffect(() => stats.end());
26
+ return () => {
27
+ if (classNames.length) stats.dom.classList.remove(...classNames);
28
+ node?.removeChild(stats.dom);
29
+ begin();
30
+ end();
31
+ };
32
+ }
33
+ }, [parent, stats, className, showPanel]);
34
+ null;
35
+ }
@@ -0,0 +1,60 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { addAfterEffect, type ThreeRef, useThree } from '@octanejs/three';
3
+ import { useEffect, useMemo } from '@octanejs/three/renderer';
4
+ import StatsImpl from 'stats-gl';
5
+
6
+ type StatsOptions = ConstructorParameters<typeof StatsImpl>[0];
7
+
8
+ export type StatsGlProps = Partial<StatsOptions> & {
9
+ id?: string;
10
+ clearStatsGlStyle?: boolean;
11
+ showPanel?: number;
12
+ className?: string;
13
+ parent?: { current: HTMLElement | null };
14
+ ref?: ThreeRef<HTMLDivElement>;
15
+ };
16
+
17
+ function assignRef<T>(ref: ThreeRef<T> | undefined, value: T | null): void {
18
+ if (Array.isArray(ref)) for (const item of ref) assignRef(item, value);
19
+ else if (typeof ref === 'function') ref(value);
20
+ else if (ref && 'current' in ref) ref.current = value;
21
+ }
22
+
23
+ export function StatsGl({
24
+ className,
25
+ parent,
26
+ id,
27
+ clearStatsGlStyle,
28
+ ref,
29
+ ...props
30
+ }: StatsGlProps) @{
31
+ const gl = useThree((state) => state.gl);
32
+ const stats = useMemo(() => {
33
+ const value = new StatsImpl(props);
34
+ value.init(gl as any);
35
+ return value;
36
+ }, [gl]);
37
+ useEffect(() => {
38
+ assignRef(ref, stats.domElement);
39
+ return () => assignRef(ref, null);
40
+ }, [ref, stats]);
41
+ useEffect(() => {
42
+ const node = (parent && parent.current) || document.body;
43
+ node?.appendChild(stats.domElement);
44
+ stats.domElement.querySelectorAll('canvas').forEach((canvas) => {
45
+ canvas.style.removeProperty('position');
46
+ });
47
+ if (id) stats.domElement.id = id;
48
+ if (clearStatsGlStyle) stats.domElement.removeAttribute('style');
49
+ stats.domElement.removeAttribute('style');
50
+ const classNames = (className ?? '').split(' ').filter((value) => value);
51
+ if (classNames.length) stats.domElement.classList.add(...classNames);
52
+ const end = addAfterEffect(() => stats.update());
53
+ return () => {
54
+ if (classNames.length) stats.domElement.classList.remove(...classNames);
55
+ node?.removeChild(stats.domElement);
56
+ end();
57
+ };
58
+ }, [parent, stats, className, id, clearStatsGlStyle]);
59
+ null;
60
+ }
@@ -0,0 +1,82 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { type ThreeElements, type ThreeRef, useLoader } from '@octanejs/three';
3
+ import { useEffect, useMemo } from '@octanejs/three/renderer';
4
+ import { DoubleSide, type Object3D } from 'three';
5
+ import { SVGLoader } from 'three-stdlib';
6
+
7
+ export interface SvgProps extends Omit<ThreeElements['object3D'], 'ref'> {
8
+ src: string;
9
+ skipFill?: boolean;
10
+ skipStrokes?: boolean;
11
+ fillMaterial?: ThreeElements['meshBasicMaterial'];
12
+ strokeMaterial?: ThreeElements['meshBasicMaterial'];
13
+ fillMeshProps?: ThreeElements['mesh'];
14
+ strokeMeshProps?: ThreeElements['mesh'];
15
+ }
16
+
17
+ export function Svg({
18
+ src,
19
+ skipFill,
20
+ skipStrokes,
21
+ fillMaterial,
22
+ strokeMaterial,
23
+ fillMeshProps,
24
+ strokeMeshProps,
25
+ ref,
26
+ ...props
27
+ }: SvgProps & { ref?: ThreeRef<Object3D> }) @{
28
+ const url = !src.startsWith('<svg') ? src : `data:image/svg+xml;utf8,${src}`;
29
+ const svg = useLoader(SVGLoader, url);
30
+ const fillShapes = useMemo(() => svg.paths.map((path) => SVGLoader.createShapes(path)), [svg]);
31
+ const strokeGeometries = useMemo(
32
+ () => skipStrokes
33
+ ? []
34
+ : svg.paths.map((path) =>
35
+ path.userData?.style.stroke === undefined || path.userData.style.stroke === 'none'
36
+ ? null
37
+ : path.subPaths.map((subPath) => SVGLoader.pointsToStroke(subPath.getPoints(), path.userData!.style)),
38
+ ),
39
+ [svg, skipStrokes],
40
+ );
41
+ useEffect(() => () => {
42
+ strokeGeometries.forEach((group) => group?.forEach((geometry) => geometry.dispose()));
43
+ }, [strokeGeometries]);
44
+ let renderOrder = 0;
45
+ <object3D ref={ref as any} {...props}>
46
+ <object3D scale={[1, -1, 1]}>
47
+ @for (const path of svg.paths; key svg.paths.indexOf(path)) {
48
+ <>
49
+ @if (!skipFill && path.userData?.style.fill !== undefined && path.userData.style.fill !== 'none') {
50
+ @for (const shape of fillShapes[svg.paths.indexOf(path)]!; key fillShapes[svg.paths.indexOf(path)]!.indexOf(shape)) {
51
+ <mesh {...fillMeshProps} renderOrder={renderOrder++}>
52
+ <shapeGeometry args={[shape as any]} />
53
+ <meshBasicMaterial
54
+ color={path.userData!.style.fill}
55
+ opacity={path.userData!.style.fillOpacity}
56
+ transparent
57
+ side={DoubleSide}
58
+ depthWrite={false}
59
+ {...fillMaterial}
60
+ />
61
+ </mesh>
62
+ }
63
+ }
64
+ @if (!skipStrokes && path.userData?.style.stroke !== undefined && path.userData.style.stroke !== 'none') {
65
+ @for (const subPath of path.subPaths; key path.subPaths.indexOf(subPath)) {
66
+ <mesh geometry={strokeGeometries[svg.paths.indexOf(path)]?.[path.subPaths.indexOf(subPath)] as any} {...strokeMeshProps} renderOrder={renderOrder++}>
67
+ <meshBasicMaterial
68
+ color={path.userData!.style.stroke}
69
+ opacity={path.userData!.style.strokeOpacity}
70
+ transparent
71
+ side={DoubleSide}
72
+ depthWrite={false}
73
+ {...strokeMaterial}
74
+ />
75
+ </mesh>
76
+ }
77
+ }
78
+ </>
79
+ }
80
+ </object3D>
81
+ </object3D>
82
+ }
@@ -0,0 +1,96 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ /// <reference path="../troika-three-text.d.ts" />
3
+ import { type ThreeElements, type ThreeRef, useThree } from '@octanejs/three';
4
+ import { use, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from '@octanejs/three/renderer';
5
+ import type { OctaneNode } from 'octane';
6
+ import type { ColorRepresentation } from 'three';
7
+ import { Text as TextMeshImpl, preloadFont } from 'troika-three-text';
8
+ import { forEachOctaneChild } from './octane-element.js';
9
+ import { resourcePromise } from './resource.js';
10
+
11
+ export type TextProps = Omit<ThreeElements['mesh'], 'ref'> & {
12
+ children: OctaneNode;
13
+ characters?: string;
14
+ color?: ColorRepresentation;
15
+ fontSize?: number;
16
+ fontWeight?: number | string;
17
+ fontStyle?: 'italic' | 'normal';
18
+ maxWidth?: number;
19
+ lineHeight?: number;
20
+ letterSpacing?: number;
21
+ textAlign?: 'left' | 'right' | 'center' | 'justify';
22
+ font?: string;
23
+ anchorX?: number | 'left' | 'center' | 'right';
24
+ anchorY?: number | 'top' | 'top-baseline' | 'middle' | 'bottom-baseline' | 'bottom';
25
+ clipRect?: [number, number, number, number];
26
+ depthOffset?: number;
27
+ direction?: 'auto' | 'ltr' | 'rtl';
28
+ overflowWrap?: 'normal' | 'break-word';
29
+ whiteSpace?: 'normal' | 'overflowWrap' | 'nowrap';
30
+ outlineWidth?: number | string;
31
+ outlineOffsetX?: number | string;
32
+ outlineOffsetY?: number | string;
33
+ outlineBlur?: number | string;
34
+ outlineColor?: ColorRepresentation;
35
+ outlineOpacity?: number;
36
+ strokeWidth?: number | string;
37
+ strokeColor?: ColorRepresentation;
38
+ strokeOpacity?: number;
39
+ fillOpacity?: number;
40
+ sdfGlyphSize?: number;
41
+ debugSDF?: boolean;
42
+ glyphGeometryDetail?: number;
43
+ onSync?: (troika: any) => void;
44
+ };
45
+
46
+ function loadTroikaFont(font?: string, characters?: string): Promise<void> {
47
+ return new Promise((resolve) => preloadFont({ font, characters }, resolve));
48
+ }
49
+
50
+ function splitTextChildren(children: OctaneNode): [OctaneNode[], string] {
51
+ const nodes: OctaneNode[] = [];
52
+ let text = '';
53
+ forEachOctaneChild(children, (child) => {
54
+ if (typeof child === 'string' || typeof child === 'number') text += child;
55
+ else nodes.push(child);
56
+ });
57
+ return [nodes, text];
58
+ }
59
+
60
+ export function Text({
61
+ sdfGlyphSize = 64,
62
+ anchorX = 'center',
63
+ anchorY = 'middle',
64
+ font,
65
+ fontSize = 1,
66
+ children,
67
+ characters,
68
+ onSync,
69
+ ref,
70
+ ...props
71
+ }: TextProps & { ref?: ThreeRef<any> }) @{
72
+ const invalidate = useThree((state) => state.invalidate);
73
+ const [troikaMesh] = useState(() => new TextMeshImpl());
74
+ const [nodes, text] = useMemo(() => splitTextChildren(children), [children]);
75
+ use(resourcePromise(loadTroikaFont, ['troika-text', font, characters]));
76
+ useLayoutEffect(() => void troikaMesh.sync(() => {
77
+ invalidate();
78
+ onSync?.(troikaMesh);
79
+ }));
80
+ useEffect(() => () => troikaMesh.dispose(), [troikaMesh]);
81
+ const localRef = useRef<any>(null);
82
+ useImperativeHandle((ref ?? null) as any, () => localRef.current, []);
83
+ <primitive
84
+ object={troikaMesh}
85
+ ref={localRef}
86
+ font={font}
87
+ text={text}
88
+ anchorX={anchorX}
89
+ anchorY={anchorY}
90
+ fontSize={fontSize}
91
+ sdfGlyphSize={sdfGlyphSize}
92
+ {...(props as any)}
93
+ >
94
+ {nodes}
95
+ </primitive>
96
+ }
@@ -0,0 +1,90 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { extend, type ThreeElements, type ThreeRef } from '@octanejs/three';
3
+ import { useImperativeHandle, useLayoutEffect, useMemo, useRef } from '@octanejs/three/renderer';
4
+ import type { OctaneNode } from 'octane';
5
+ import * as THREE from 'three';
6
+ import { mergeVertices, TextGeometry, type TextGeometryParameters } from 'three-stdlib';
7
+ import { useFont, type FontData } from './useFont.three.tsrx';
8
+ import { forEachOctaneChild } from './octane-element.js';
9
+
10
+ const RenamedTextGeometry = extend(TextGeometry);
11
+
12
+ export type Text3DProps = Omit<ThreeElements['mesh'], 'ref'> & {
13
+ font: FontData | string;
14
+ bevelSegments?: number;
15
+ smooth?: number;
16
+ letterSpacing?: number;
17
+ lineHeight?: number;
18
+ children?: OctaneNode;
19
+ } & Omit<TextGeometryParameters, 'font'>;
20
+
21
+ const textTypes = new Set(['string', 'number']);
22
+
23
+ function splitTextChildren(children: OctaneNode): [string, ...OctaneNode[]] {
24
+ let label = '';
25
+ const rest: OctaneNode[] = [];
26
+ forEachOctaneChild(children, (child) => {
27
+ if (textTypes.has(typeof child)) label += String(child);
28
+ else rest.push(child);
29
+ });
30
+ return [label, ...rest];
31
+ }
32
+
33
+ export function Text3D({
34
+ font: fontInput,
35
+ letterSpacing = 0,
36
+ lineHeight = 1,
37
+ size = 1,
38
+ height = 0.2,
39
+ bevelThickness = 0.1,
40
+ bevelSize = 0.01,
41
+ bevelEnabled = false,
42
+ bevelOffset = 0,
43
+ bevelSegments = 4,
44
+ curveSegments = 8,
45
+ smooth,
46
+ children,
47
+ ref,
48
+ ...props
49
+ }: Text3DProps & { ref?: ThreeRef<THREE.Mesh> }) @{
50
+ const mesh = useRef<THREE.Mesh | null>(null);
51
+ const font = useFont(fontInput);
52
+ const options = useMemo(() => ({
53
+ font,
54
+ size,
55
+ height,
56
+ bevelThickness,
57
+ bevelSize,
58
+ bevelEnabled,
59
+ bevelSegments,
60
+ bevelOffset,
61
+ curveSegments,
62
+ letterSpacing,
63
+ lineHeight,
64
+ }), [
65
+ font,
66
+ size,
67
+ height,
68
+ bevelThickness,
69
+ bevelSize,
70
+ bevelEnabled,
71
+ bevelSegments,
72
+ bevelOffset,
73
+ curveSegments,
74
+ letterSpacing,
75
+ lineHeight,
76
+ ]);
77
+ const [label, ...rest] = useMemo(() => splitTextChildren(children), [children]);
78
+ const args = useMemo(() => [label, options] as const, [label, options]);
79
+ useLayoutEffect(() => {
80
+ if (smooth && mesh.current) {
81
+ mesh.current.geometry = mergeVertices(mesh.current.geometry as any, smooth) as any;
82
+ mesh.current.geometry.computeVertexNormals();
83
+ }
84
+ }, [args, smooth]);
85
+ useImperativeHandle((ref ?? null) as any, () => mesh.current!, []);
86
+ <mesh {...props} ref={mesh}>
87
+ <RenamedTextGeometry args={args as any} />
88
+ {rest}
89
+ </mesh>
90
+ }
@@ -0,0 +1,72 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { useLoader, useThree } from '@octanejs/three';
3
+ import { useEffect, useLayoutEffect, useMemo } from '@octanejs/three/renderer';
4
+ import { Texture as ThreeTexture, TextureLoader } from 'three';
5
+ import { slotDefault } from './compiler-slot.js';
6
+ import { resolveRenderChildren, type RenderChildren } from './octane-children.js';
7
+
8
+ export const IsObject = (url: unknown): url is Record<string, string> =>
9
+ url === Object(url) && !Array.isArray(url) && typeof url !== 'function';
10
+
11
+ type TextureArray<T> = T extends string[] ? ThreeTexture[] : never;
12
+ type TextureRecord<T> =
13
+ T extends Record<string, string> ? { [key in keyof T]: ThreeTexture } : never;
14
+ type SingleTexture<T> = T extends string ? ThreeTexture : never;
15
+
16
+ export type MappedTextureType<T extends string[] | string | Record<string, string>> =
17
+ TextureArray<T> | TextureRecord<T> | SingleTexture<T>;
18
+
19
+ export function useTexture<Url extends string[] | string | Record<string, string>>(
20
+ input: Url,
21
+ onLoad?: (texture: MappedTextureType<Url>) => void,
22
+ ): MappedTextureType<Url> {
23
+ onLoad = slotDefault(onLoad, undefined);
24
+ const gl = useThree((state) => state.gl);
25
+ const textures = useLoader(
26
+ TextureLoader,
27
+ IsObject(input) ? Object.values(input) : input,
28
+ ) as MappedTextureType<Url>;
29
+
30
+ useLayoutEffect(() => {
31
+ onLoad?.(textures);
32
+ }, [onLoad]);
33
+
34
+ useEffect(() => {
35
+ if (!('initTexture' in gl)) return;
36
+ let textureArray: ThreeTexture[] = [];
37
+ if (Array.isArray(textures)) textureArray = textures;
38
+ else if (textures instanceof ThreeTexture) textureArray = [textures];
39
+ else if (IsObject(textures)) textureArray = Object.values(textures);
40
+ for (const texture of textureArray) {
41
+ if (texture instanceof ThreeTexture) gl.initTexture(texture);
42
+ }
43
+ }, [gl, textures]);
44
+
45
+ return useMemo(() => {
46
+ if (!IsObject(input)) return textures;
47
+ const keyed = {} as MappedTextureType<Url>;
48
+ let index = 0;
49
+ for (const key in input) {
50
+ (keyed as Record<string, ThreeTexture>)[key] = (textures as ThreeTexture[])[index++];
51
+ }
52
+ return keyed;
53
+ }, [input, textures]);
54
+ }
55
+
56
+ useTexture.preload = (url: string | string[]) => useLoader.preload(TextureLoader, url);
57
+ useTexture.clear = (input: string | string[]) => useLoader.clear(TextureLoader, input);
58
+
59
+ export function Texture({
60
+ children,
61
+ input,
62
+ onLoad,
63
+ }: {
64
+ children?: RenderChildren<ReturnType<typeof useTexture>>;
65
+ input: Parameters<typeof useTexture>[0];
66
+ onLoad?: Parameters<typeof useTexture>[1];
67
+ }) @{
68
+ const texture = useTexture(input, onLoad);
69
+ <>
70
+ {resolveRenderChildren(children, texture)}
71
+ </>
72
+ }