@octanejs/drei 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/UPSTREAM.md +92 -0
- package/package.json +96 -0
- package/src/config.ts +14 -0
- package/src/core/AccumulativeShadows.three.tsrx +313 -0
- package/src/core/AdaptivePerformance.three.tsrx +49 -0
- package/src/core/AsciiRenderer.three.tsrx +61 -0
- package/src/core/Bounds.three.tsrx +323 -0
- package/src/core/Bvh.three.tsrx +116 -0
- package/src/core/CameraControls.three.tsrx +181 -0
- package/src/core/CameraShake.three.tsrx +104 -0
- package/src/core/Caustics.three.tsrx +107 -0
- package/src/core/Center.three.tsrx +145 -0
- package/src/core/Clone.three.tsrx +167 -0
- package/src/core/Cloud.three.tsrx +314 -0
- package/src/core/ComputedAttribute.three.tsrx +27 -0
- package/src/core/ContactShadows.three.tsrx +106 -0
- package/src/core/CubeTexture.three.tsrx +32 -0
- package/src/core/CurveModifier.three.tsrx +37 -0
- package/src/core/Decal.three.tsrx +132 -0
- package/src/core/Detailed.three.tsrx +28 -0
- package/src/core/DetectGPU.three.tsrx +22 -0
- package/src/core/Effects.three.tsrx +123 -0
- package/src/core/Environment.three.tsrx +220 -0
- package/src/core/Example.three.tsrx +56 -0
- package/src/core/Fbo.three.tsrx +70 -0
- package/src/core/Fbx.three.tsrx +25 -0
- package/src/core/Fisheye.three.tsrx +82 -0
- package/src/core/GizmoHelper.three.tsrx +115 -0
- package/src/core/GizmoViewcube.three.tsrx +82 -0
- package/src/core/GizmoViewport.three.tsrx +122 -0
- package/src/core/Gltf.three.tsrx +62 -0
- package/src/core/GradientTexture.three.tsrx +66 -0
- package/src/core/Grid.three.tsrx +170 -0
- package/src/core/Helper.three.tsrx +56 -0
- package/src/core/Hud.three.tsrx +52 -0
- package/src/core/Image.three.tsrx +146 -0
- package/src/core/Instances.three.tsrx +332 -0
- package/src/core/Ktx2.three.tsrx +59 -0
- package/src/core/Lightformer.three.tsrx +75 -0
- package/src/core/MarchingCubes.three.tsrx +125 -0
- package/src/core/MeshPortalMaterial.three.tsrx +103 -0
- package/src/core/MeshReflectorMaterial.three.tsrx +67 -0
- package/src/core/MeshRefractionMaterial.three.tsrx +70 -0
- package/src/core/MeshTransmissionMaterial.three.tsrx +71 -0
- package/src/core/MotionPathControls.three.tsrx +139 -0
- package/src/core/MultiMaterial.three.tsrx +32 -0
- package/src/core/Outlines.three.tsrx +162 -0
- package/src/core/PerformanceMonitor.three.tsrx +144 -0
- package/src/core/PointerLockControls.three.tsrx +84 -0
- package/src/core/Points.three.tsrx +242 -0
- package/src/core/PositionalAudio.three.tsrx +76 -0
- package/src/core/Preload.three.tsrx +40 -0
- package/src/core/Progress.three.tsrx +47 -0
- package/src/core/RenderCubeTexture.three.tsrx +111 -0
- package/src/core/RenderTexture.three.tsrx +102 -0
- package/src/core/Resize.three.tsrx +59 -0
- package/src/core/RoundedBox.three.tsrx +106 -0
- package/src/core/Sampler.three.tsrx +116 -0
- package/src/core/ScreenSizer.three.tsrx +43 -0
- package/src/core/ScreenSpace.three.tsrx +21 -0
- package/src/core/Segments.three.tsrx +119 -0
- package/src/core/Sky.three.tsrx +63 -0
- package/src/core/Sparkles.three.tsrx +145 -0
- package/src/core/Splat.three.tsrx +52 -0
- package/src/core/SpotLight.three.tsrx +350 -0
- package/src/core/SpriteAnimator.three.tsrx +338 -0
- package/src/core/Stage.three.tsrx +112 -0
- package/src/core/Stars.three.tsrx +103 -0
- package/src/core/Stats.three.tsrx +35 -0
- package/src/core/StatsGl.three.tsrx +60 -0
- package/src/core/Svg.three.tsrx +82 -0
- package/src/core/Text.three.tsrx +96 -0
- package/src/core/Text3D.three.tsrx +90 -0
- package/src/core/Texture.three.tsrx +72 -0
- package/src/core/Trail.three.tsrx +167 -0
- package/src/core/TrailTexture.three.tsrx +171 -0
- package/src/core/TransformControls.three.tsrx +155 -0
- package/src/core/VideoTexture.three.tsrx +116 -0
- package/src/core/Wireframe.three.tsrx +92 -0
- package/src/core/basic-controls.three.tsrx +353 -0
- package/src/core/calculateScaleFactor.ts +42 -0
- package/src/core/cameras.three.tsrx +230 -0
- package/src/core/catalog-resource.ts +12 -0
- package/src/core/catalog-textures.three.tsrx +105 -0
- package/src/core/compiler-slot.ts +9 -0
- package/src/core/effects-utils.ts +8 -0
- package/src/core/environment-loader.ts +87 -0
- package/src/core/font-resource.ts +31 -0
- package/src/core/gltf-loader.ts +33 -0
- package/src/core/gradient-types.ts +4 -0
- package/src/core/index.ts +295 -0
- package/src/core/lines.three.tsrx +329 -0
- package/src/core/meshBounds.ts +26 -0
- package/src/core/motion-materials.three.tsrx +93 -0
- package/src/core/octane-children.ts +18 -0
- package/src/core/octane-element.ts +16 -0
- package/src/core/resource.ts +29 -0
- package/src/core/scene-motion.three.tsrx +183 -0
- package/src/core/shaderMaterial.ts +50 -0
- package/src/core/shadows.three.tsrx +164 -0
- package/src/core/shapes.three.tsrx +170 -0
- package/src/core/simple-materials.three.tsrx +124 -0
- package/src/core/softShadows.three.tsrx +151 -0
- package/src/core/splat-resource.ts +598 -0
- package/src/core/useAnimations.three.tsrx +57 -0
- package/src/core/useAspect.three.tsrx +10 -0
- package/src/core/useBoxProjectedEnv.three.tsrx +113 -0
- package/src/core/useCamera.three.tsrx +26 -0
- package/src/core/useContextBridge.three.tsrx +25 -0
- package/src/core/useDepthBuffer.three.tsrx +35 -0
- package/src/core/useEnvironment.three.tsrx +81 -0
- package/src/core/useFont.three.tsrx +12 -0
- package/src/core/useIntersect.three.tsrx +38 -0
- package/src/core/useSpriteLoader.three.tsrx +249 -0
- package/src/core/video-texture-resource.ts +14 -0
- package/src/index.ts +2 -0
- package/src/materials/BlurPass.ts +94 -0
- package/src/materials/CausticsMaterials.ts +197 -0
- package/src/materials/ConvolutionMaterial.ts +65 -0
- package/src/materials/MeshReflectorMaterial.ts +180 -0
- package/src/materials/MeshRefractionMaterial.ts +131 -0
- package/src/materials/MeshTransmissionMaterial.ts +182 -0
- package/src/materials/PortalMaterial.ts +38 -0
- package/src/materials/WireframeMaterial.ts +241 -0
- package/src/materials/makeSDFGenerator.ts +155 -0
- package/src/troika-three-text.d.ts +19 -0
- package/src/web/CycleRaycast.three.tsrx +81 -0
- package/src/web/DragControls.three.tsrx +82 -0
- package/src/web/FaceControls.three.tsrx +178 -0
- package/src/web/FaceLandmarker.three.tsrx +48 -0
- package/src/web/Facemesh.three.tsrx +464 -0
- package/src/web/Html.three.tsrx +390 -0
- package/src/web/KeyboardControls.three.tsrx +98 -0
- package/src/web/Loader.tsrx +61 -0
- package/src/web/PresentationControls.three.tsrx +63 -0
- package/src/web/ScrollControls.three.tsrx +120 -0
- package/src/web/Select.three.tsrx +155 -0
- package/src/web/View.three.tsrx +46 -0
- package/src/web/face-landmarker-resource.ts +18 -0
- package/src/web/facemesh-data.ts +54 -0
- package/src/web/index.ts +41 -0
- package/src/web/keyboard-controls-types.ts +33 -0
- package/src/web/media-textures.three.tsrx +50 -0
- package/src/web/pivotControls/annotation.tsrx +17 -0
- package/src/web/pivotControls/context.ts +38 -0
- package/src/web/pivotControls/index.three.tsrx +877 -0
- package/src/web/scroll-html-root.tsrx +27 -0
- package/src/web/useCursor.three.tsrx +19 -0
- package/src/web/useGesture.ts +27 -0
- package/status.json +14 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { applyProps, type Vector3 as Vector3Prop } from '@octanejs/three';
|
|
2
|
+
import { useLayoutEffect, useMemo, useRef, useState } from '@octanejs/three/renderer';
|
|
3
|
+
import * as THREE from 'three';
|
|
4
|
+
import { slotDefault } from './compiler-slot.js';
|
|
5
|
+
|
|
6
|
+
const worldposReplace = /* glsl */ `
|
|
7
|
+
#if defined( USE_ENVMAP ) || defined( DISTANCE ) || defined ( USE_SHADOWMAP )
|
|
8
|
+
vec4 worldPosition = modelMatrix * vec4( transformed, 1.0 );
|
|
9
|
+
#ifdef BOX_PROJECTED_ENV_MAP
|
|
10
|
+
vWorldPosition = worldPosition.xyz;
|
|
11
|
+
#endif
|
|
12
|
+
#endif
|
|
13
|
+
`;
|
|
14
|
+
|
|
15
|
+
const boxProjectDefinitions = /* glsl */ `
|
|
16
|
+
#ifdef BOX_PROJECTED_ENV_MAP
|
|
17
|
+
uniform vec3 envMapSize;
|
|
18
|
+
uniform vec3 envMapPosition;
|
|
19
|
+
varying vec3 vWorldPosition;
|
|
20
|
+
|
|
21
|
+
vec3 parallaxCorrectNormal( vec3 v, vec3 cubeSize, vec3 cubePos ) {
|
|
22
|
+
vec3 nDir = normalize( v );
|
|
23
|
+
vec3 rbmax = ( .5 * cubeSize + cubePos - vWorldPosition ) / nDir;
|
|
24
|
+
vec3 rbmin = ( -.5 * cubeSize + cubePos - vWorldPosition ) / nDir;
|
|
25
|
+
vec3 rbminmax;
|
|
26
|
+
rbminmax.x = ( nDir.x > 0. ) ? rbmax.x : rbmin.x;
|
|
27
|
+
rbminmax.y = ( nDir.y > 0. ) ? rbmax.y : rbmin.y;
|
|
28
|
+
rbminmax.z = ( nDir.z > 0. ) ? rbmax.z : rbmin.z;
|
|
29
|
+
float correction = min( min( rbminmax.x, rbminmax.y ), rbminmax.z );
|
|
30
|
+
vec3 boxIntersection = vWorldPosition + nDir * correction;
|
|
31
|
+
return boxIntersection - cubePos;
|
|
32
|
+
}
|
|
33
|
+
#endif
|
|
34
|
+
`;
|
|
35
|
+
|
|
36
|
+
const getIBLIrradiancePatch = /* glsl */ `
|
|
37
|
+
#ifdef BOX_PROJECTED_ENV_MAP
|
|
38
|
+
worldNormal = parallaxCorrectNormal( worldNormal, envMapSize, envMapPosition );
|
|
39
|
+
#endif
|
|
40
|
+
`;
|
|
41
|
+
|
|
42
|
+
const getIBLRadiancePatch = /* glsl */ `
|
|
43
|
+
#ifdef BOX_PROJECTED_ENV_MAP
|
|
44
|
+
reflectVec = parallaxCorrectNormal( reflectVec, envMapSize, envMapPosition );
|
|
45
|
+
#endif
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
interface MaterialShader {
|
|
49
|
+
vertexShader: string;
|
|
50
|
+
fragmentShader: string;
|
|
51
|
+
defines: Record<string, string | number | boolean> | undefined;
|
|
52
|
+
uniforms: Record<string, THREE.IUniform>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function boxProjectedEnvMap(
|
|
56
|
+
shader: MaterialShader,
|
|
57
|
+
envMapPosition: THREE.Vector3,
|
|
58
|
+
envMapSize: THREE.Vector3,
|
|
59
|
+
) {
|
|
60
|
+
shader.defines!.BOX_PROJECTED_ENV_MAP = true;
|
|
61
|
+
shader.uniforms.envMapPosition = { value: envMapPosition };
|
|
62
|
+
shader.uniforms.envMapSize = { value: envMapSize };
|
|
63
|
+
shader.vertexShader = `
|
|
64
|
+
varying vec3 vWorldPosition;
|
|
65
|
+
${shader.vertexShader.replace(
|
|
66
|
+
'#include <worldpos_vertex>',
|
|
67
|
+
worldposReplace,
|
|
68
|
+
)}`;
|
|
69
|
+
shader.fragmentShader = `
|
|
70
|
+
${boxProjectDefinitions}
|
|
71
|
+
${shader.fragmentShader.replace(
|
|
72
|
+
'#include <envmap_physical_pars_fragment>',
|
|
73
|
+
THREE.ShaderChunk.envmap_physical_pars_fragment,
|
|
74
|
+
).replace(
|
|
75
|
+
'vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );',
|
|
76
|
+
`vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );
|
|
77
|
+
${getIBLIrradiancePatch}
|
|
78
|
+
`,
|
|
79
|
+
).replace(
|
|
80
|
+
'reflectVec = inverseTransformDirection( reflectVec, viewMatrix );',
|
|
81
|
+
`reflectVec = inverseTransformDirection( reflectVec, viewMatrix );
|
|
82
|
+
${getIBLRadiancePatch}
|
|
83
|
+
`,
|
|
84
|
+
)}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function useBoxProjectedEnv(
|
|
88
|
+
position: Vector3Prop = new THREE.Vector3(),
|
|
89
|
+
size: Vector3Prop = new THREE.Vector3(),
|
|
90
|
+
) {
|
|
91
|
+
position = slotDefault(position, new THREE.Vector3());
|
|
92
|
+
size = slotDefault(size, new THREE.Vector3());
|
|
93
|
+
const [config] = useState(() => ({ position: new THREE.Vector3(), size: new THREE.Vector3() }));
|
|
94
|
+
applyProps(config as any, { position, size });
|
|
95
|
+
const ref = useRef<THREE.Material | null>(null);
|
|
96
|
+
const spread = useMemo(
|
|
97
|
+
() => ({
|
|
98
|
+
ref,
|
|
99
|
+
onBeforeCompile: (shader: MaterialShader) => boxProjectedEnvMap(
|
|
100
|
+
shader,
|
|
101
|
+
config.position,
|
|
102
|
+
config.size,
|
|
103
|
+
),
|
|
104
|
+
customProgramCacheKey: () =>
|
|
105
|
+
JSON.stringify(config.position.toArray()) + JSON.stringify(config.size.toArray()),
|
|
106
|
+
}),
|
|
107
|
+
[...config.position.toArray(), ...config.size.toArray()],
|
|
108
|
+
);
|
|
109
|
+
useLayoutEffect(() => {
|
|
110
|
+
if (ref.current) ref.current.needsUpdate = true;
|
|
111
|
+
}, [config]);
|
|
112
|
+
return spread;
|
|
113
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { applyProps, type RefObject, useThree } from '@octanejs/three';
|
|
2
|
+
import { useState } from '@octanejs/three/renderer';
|
|
3
|
+
import { Camera, type Intersection, Raycaster } from 'three';
|
|
4
|
+
import { slotDefault } from './compiler-slot.js';
|
|
5
|
+
|
|
6
|
+
export function useCamera(camera: Camera | RefObject<Camera>, props?: Partial<Raycaster>) {
|
|
7
|
+
props = slotDefault(props, undefined);
|
|
8
|
+
const pointer = useThree((state) => state.pointer);
|
|
9
|
+
const [raycast] = useState(() => {
|
|
10
|
+
const raycaster = new Raycaster();
|
|
11
|
+
if (props) applyProps(raycaster, props);
|
|
12
|
+
return function (
|
|
13
|
+
this: { constructor: { prototype: { raycast?: Function } } },
|
|
14
|
+
_: Raycaster,
|
|
15
|
+
intersects: Intersection[],
|
|
16
|
+
): void {
|
|
17
|
+
const activeCamera =
|
|
18
|
+
camera instanceof Camera ? camera : camera.current;
|
|
19
|
+
if (!activeCamera) return;
|
|
20
|
+
raycaster.setFromCamera(pointer, activeCamera);
|
|
21
|
+
const implementation = this.constructor.prototype.raycast?.bind(this);
|
|
22
|
+
implementation?.(raycaster, intersects);
|
|
23
|
+
};
|
|
24
|
+
});
|
|
25
|
+
return raycast;
|
|
26
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** @jsxImportSource @octanejs/three/intrinsics */
|
|
2
|
+
import { type Context, type OctaneNode, useContext, useMemo, useRef } from 'octane';
|
|
3
|
+
import { defineUniversalComponent, universalComponent, universalContext, type UniversalRenderable } from 'octane/universal';
|
|
4
|
+
import { stripTrailingSlot } from './compiler-slot.js';
|
|
5
|
+
|
|
6
|
+
type BridgeProps = { children?: OctaneNode };
|
|
7
|
+
type BridgeRendererProps = BridgeProps & { contexts: Array<Context<any>>; values: { current: unknown[] } };
|
|
8
|
+
|
|
9
|
+
function BridgeRenderer({ children, contexts, values }: BridgeRendererProps) @{
|
|
10
|
+
let result = children as UniversalRenderable;
|
|
11
|
+
for (let index = contexts.length - 1; index >= 0; index--) {
|
|
12
|
+
result = universalContext(contexts[index]!, values.current[index], result);
|
|
13
|
+
}
|
|
14
|
+
<>{result}</>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function useContextBridge(...contexts: Array<Context<any>>) {
|
|
18
|
+
contexts = stripTrailingSlot(contexts);
|
|
19
|
+
const values = useRef<unknown[]>([]);
|
|
20
|
+
values.current = contexts.map((context) => useContext(context));
|
|
21
|
+
return useMemo(() => defineUniversalComponent<BridgeProps>(
|
|
22
|
+
'three',
|
|
23
|
+
(props) => universalComponent('three', BridgeRenderer as any, { ...props, contexts, values }),
|
|
24
|
+
), []);
|
|
25
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useFrame, useThree } from '@octanejs/three';
|
|
2
|
+
import { useMemo } from '@octanejs/three/renderer';
|
|
3
|
+
import { DepthFormat, DepthTexture, UnsignedShortType } from 'three';
|
|
4
|
+
import { slotDefault } from './compiler-slot.js';
|
|
5
|
+
import { useFBO } from './Fbo.three.tsrx';
|
|
6
|
+
|
|
7
|
+
type DepthBufferOptions = {
|
|
8
|
+
size?: number;
|
|
9
|
+
frames?: number;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export function useDepthBuffer(options: DepthBufferOptions = {}) {
|
|
13
|
+
const { size = 256, frames = Infinity } = slotDefault(options, {});
|
|
14
|
+
const dpr = useThree((state) => state.viewport.dpr);
|
|
15
|
+
const { width, height } = useThree((state) => state.size);
|
|
16
|
+
const targetWidth = size || width * dpr;
|
|
17
|
+
const targetHeight = size || height * dpr;
|
|
18
|
+
const depthConfig = useMemo(() => {
|
|
19
|
+
const depthTexture = new DepthTexture(targetWidth, targetHeight);
|
|
20
|
+
depthTexture.format = DepthFormat;
|
|
21
|
+
depthTexture.type = UnsignedShortType;
|
|
22
|
+
return { depthTexture };
|
|
23
|
+
}, [targetWidth, targetHeight]);
|
|
24
|
+
let count = 0;
|
|
25
|
+
const depthFbo = useFBO(targetWidth, targetHeight, depthConfig);
|
|
26
|
+
useFrame((state) => {
|
|
27
|
+
if (frames === Infinity || count < frames) {
|
|
28
|
+
state.gl.setRenderTarget?.(depthFbo);
|
|
29
|
+
state.gl.render(state.scene, state.camera);
|
|
30
|
+
state.gl.setRenderTarget?.(null);
|
|
31
|
+
count++;
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
return depthFbo.depthTexture;
|
|
35
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { useLoader, useThree } from '@octanejs/three';
|
|
2
|
+
import { useLayoutEffect } from '@octanejs/three/renderer';
|
|
3
|
+
import {
|
|
4
|
+
CubeReflectionMapping,
|
|
5
|
+
EquirectangularReflectionMapping,
|
|
6
|
+
type CubeTexture,
|
|
7
|
+
type Loader,
|
|
8
|
+
type Texture,
|
|
9
|
+
} from 'three';
|
|
10
|
+
import {
|
|
11
|
+
defaultEnvironmentFiles,
|
|
12
|
+
environmentExtension,
|
|
13
|
+
environmentLoader,
|
|
14
|
+
type EnvironmentLoaderProps,
|
|
15
|
+
resolveEnvironmentOptions,
|
|
16
|
+
} from './environment-loader.js';
|
|
17
|
+
import { slotDefault } from './compiler-slot.js';
|
|
18
|
+
|
|
19
|
+
export function useEnvironment(options: Partial<EnvironmentLoaderProps> = {}) {
|
|
20
|
+
options = slotDefault(options, {});
|
|
21
|
+
const { files, path, colorSpace, extensions } = resolveEnvironmentOptions(options);
|
|
22
|
+
const { extension, isCubemap, multiple } = environmentExtension(files);
|
|
23
|
+
const loader = environmentLoader(extension);
|
|
24
|
+
if (!loader) throw new Error(`useEnvironment: Unrecognized file extension: ${files}`);
|
|
25
|
+
const gl = useThree((state) => state.gl);
|
|
26
|
+
|
|
27
|
+
useLayoutEffect(() => {
|
|
28
|
+
if (extension !== 'webp' && extension !== 'jpg' && extension !== 'jpeg') return;
|
|
29
|
+
const clearGainmapTexture = () => useLoader.clear(
|
|
30
|
+
loader as any,
|
|
31
|
+
(multiple ? [files] : files) as any,
|
|
32
|
+
);
|
|
33
|
+
const canvas = gl.domElement as EventTarget;
|
|
34
|
+
canvas.addEventListener('webglcontextlost', clearGainmapTexture, { once: true });
|
|
35
|
+
}, [files, gl.domElement]);
|
|
36
|
+
|
|
37
|
+
const loaderResult = useLoader(loader as any, (multiple ? [files] : files) as any, (
|
|
38
|
+
concreteLoader: any,
|
|
39
|
+
) => {
|
|
40
|
+
if (extension === 'webp' || extension === 'jpg' || extension === 'jpeg') {
|
|
41
|
+
concreteLoader.setRenderer(gl);
|
|
42
|
+
}
|
|
43
|
+
concreteLoader.setPath?.(path);
|
|
44
|
+
extensions?.(concreteLoader as Loader);
|
|
45
|
+
}) as Texture | Texture[];
|
|
46
|
+
let texture: Texture | CubeTexture = multiple
|
|
47
|
+
? (loaderResult as Texture[])[0]
|
|
48
|
+
: loaderResult as Texture;
|
|
49
|
+
if (extension === 'jpg' || extension === 'jpeg' || extension === 'webp') {
|
|
50
|
+
texture = (texture as any).renderTarget?.texture;
|
|
51
|
+
}
|
|
52
|
+
texture.mapping = isCubemap ? CubeReflectionMapping : EquirectangularReflectionMapping;
|
|
53
|
+
texture.colorSpace = colorSpace ?? (isCubemap ? 'srgb' : 'srgb-linear');
|
|
54
|
+
return texture;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type PreloadOptions = Omit<EnvironmentLoaderProps, 'encoding'>;
|
|
58
|
+
useEnvironment.preload = (preloadOptions?: PreloadOptions) => {
|
|
59
|
+
const { files, path, extensions } = resolveEnvironmentOptions({
|
|
60
|
+
files: defaultEnvironmentFiles,
|
|
61
|
+
...preloadOptions,
|
|
62
|
+
});
|
|
63
|
+
const { extension, multiple } = environmentExtension(files);
|
|
64
|
+
if (extension === 'webp' || extension === 'jpg' || extension === 'jpeg') {
|
|
65
|
+
throw new Error('useEnvironment: Preloading gainmaps is not supported');
|
|
66
|
+
}
|
|
67
|
+
const loader = environmentLoader(extension);
|
|
68
|
+
if (!loader) throw new Error(`useEnvironment: Unrecognized file extension: ${files}`);
|
|
69
|
+
useLoader.preload(loader as any, (multiple ? [files] : files) as any, (concreteLoader: any) => {
|
|
70
|
+
concreteLoader.setPath?.(path);
|
|
71
|
+
extensions?.(concreteLoader as Loader);
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
useEnvironment.clear = (clearOptions?: Pick<EnvironmentLoaderProps, 'files' | 'preset'>) => {
|
|
76
|
+
const { files } = resolveEnvironmentOptions({ files: defaultEnvironmentFiles, ...clearOptions });
|
|
77
|
+
const { extension, multiple } = environmentExtension(files);
|
|
78
|
+
const loader = environmentLoader(extension);
|
|
79
|
+
if (!loader) throw new Error(`useEnvironment: Unrecognized file extension: ${files}`);
|
|
80
|
+
useLoader.clear(loader as any, (multiple ? [files] : files) as any);
|
|
81
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { use } from '@octanejs/three/renderer';
|
|
2
|
+
import { loadFont, type FontInput } from './font-resource.js';
|
|
3
|
+
import { clearResource, preloadResource, resourcePromise } from './resource.js';
|
|
4
|
+
|
|
5
|
+
export type { FontData, Glyph } from './font-resource.js';
|
|
6
|
+
|
|
7
|
+
export function useFont(font: FontInput) {
|
|
8
|
+
return use(resourcePromise(loadFont, [font]));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
useFont.preload = (font: FontInput) => preloadResource(loadFont, [font]);
|
|
12
|
+
useFont.clear = (font: FontInput) => clearResource([font]);
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { addAfterEffect, addEffect } from '@octanejs/three';
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef } from '@octanejs/three/renderer';
|
|
3
|
+
import type { Object3D } from 'three';
|
|
4
|
+
|
|
5
|
+
export function useIntersect<T extends Object3D>(onChange: (visible: boolean) => void) {
|
|
6
|
+
const ref = useRef<T | null>(null);
|
|
7
|
+
const check = useRef(false);
|
|
8
|
+
const previous = useRef(false);
|
|
9
|
+
const callback = useRef(onChange);
|
|
10
|
+
useLayoutEffect(() => {
|
|
11
|
+
callback.current = onChange;
|
|
12
|
+
}, [onChange]);
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const object = ref.current;
|
|
15
|
+
if (!object) return;
|
|
16
|
+
const removeBefore = addEffect(() => {
|
|
17
|
+
check.current = false;
|
|
18
|
+
return true;
|
|
19
|
+
});
|
|
20
|
+
const oldOnBeforeRender = object.onBeforeRender;
|
|
21
|
+
object.onBeforeRender = () => {
|
|
22
|
+
check.current = true;
|
|
23
|
+
};
|
|
24
|
+
const removeAfter = addAfterEffect(() => {
|
|
25
|
+
if (check.current !== previous.current) {
|
|
26
|
+
previous.current = check.current;
|
|
27
|
+
callback.current(previous.current);
|
|
28
|
+
}
|
|
29
|
+
return true;
|
|
30
|
+
});
|
|
31
|
+
return () => {
|
|
32
|
+
object.onBeforeRender = oldOnBeforeRender;
|
|
33
|
+
removeBefore();
|
|
34
|
+
removeAfter();
|
|
35
|
+
};
|
|
36
|
+
}, []);
|
|
37
|
+
return ref;
|
|
38
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { useLoader, useThree } from '@octanejs/three';
|
|
2
|
+
import { useCallback, useLayoutEffect, useMemo, useRef, useState } from '@octanejs/three/renderer';
|
|
3
|
+
import * as THREE from 'three';
|
|
4
|
+
import { slotDefault } from './compiler-slot.js';
|
|
5
|
+
|
|
6
|
+
export type Size = { w: number; h: number };
|
|
7
|
+
|
|
8
|
+
export type FrameData = {
|
|
9
|
+
frame: { x: number; y: number; w: number; h: number };
|
|
10
|
+
scaleRatio?: number;
|
|
11
|
+
rotated: boolean;
|
|
12
|
+
trimmed: boolean;
|
|
13
|
+
spriteSourceSize: { x: number; y: number; w: number; h: number };
|
|
14
|
+
sourceSize: Size;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type MetaData = {
|
|
18
|
+
version: string;
|
|
19
|
+
size: Size;
|
|
20
|
+
rows: number;
|
|
21
|
+
columns: number;
|
|
22
|
+
frameWidth: number;
|
|
23
|
+
frameHeight: number;
|
|
24
|
+
scale: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type Frames = Record<string, FrameData[]> | FrameData[];
|
|
28
|
+
|
|
29
|
+
export type SpriteData = { frames: Frames; meta: MetaData };
|
|
30
|
+
|
|
31
|
+
type SpriteMetaDimension = { row: number; col: number };
|
|
32
|
+
|
|
33
|
+
type SourceFrame = FrameData & { filename?: string };
|
|
34
|
+
|
|
35
|
+
export const getFirstFrame = (frames: SpriteData['frames'], frameName?: string): FrameData => {
|
|
36
|
+
if (Array.isArray(frames)) return frames[0];
|
|
37
|
+
const key = frameName ?? Object.keys(frames)[0];
|
|
38
|
+
return frames[key][0];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export const checkIfFrameIsEmpty = (frameData: Uint8ClampedArray): boolean => {
|
|
42
|
+
for (let index = 3; index < frameData.length; index += 4) {
|
|
43
|
+
if (frameData[index] !== 0) return false;
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export function useSpriteLoader<Url extends string>(
|
|
49
|
+
input: Url | null,
|
|
50
|
+
json?: string | null,
|
|
51
|
+
animationNames?: string[] | null,
|
|
52
|
+
numberOfFrames?: number | null,
|
|
53
|
+
onLoad?: (texture: THREE.Texture, textureData?: SpriteData | null) => void,
|
|
54
|
+
canvasRenderingContext2DSettings?: CanvasRenderingContext2DSettings,
|
|
55
|
+
) {
|
|
56
|
+
json = slotDefault(json, undefined);
|
|
57
|
+
animationNames = slotDefault(animationNames, undefined);
|
|
58
|
+
numberOfFrames = slotDefault(numberOfFrames, undefined);
|
|
59
|
+
onLoad = slotDefault(onLoad, undefined);
|
|
60
|
+
canvasRenderingContext2DSettings = slotDefault(canvasRenderingContext2DSettings, undefined);
|
|
61
|
+
const viewportRef = useRef(useThree((state) => state.viewport));
|
|
62
|
+
const spriteDataRef = useRef<SpriteData | null>(null);
|
|
63
|
+
const totalFrames = useRef(0);
|
|
64
|
+
const inputRef = useRef(input);
|
|
65
|
+
const jsonRef = useRef(json);
|
|
66
|
+
const animationFramesRef = useRef(animationNames);
|
|
67
|
+
const [spriteData, setSpriteData] = useState<SpriteData | null>(null);
|
|
68
|
+
const [spriteTexture, setSpriteTexture] = useState<THREE.Texture>(new THREE.Texture());
|
|
69
|
+
const textureLoader = useMemo(() => new THREE.TextureLoader(), []);
|
|
70
|
+
const [spriteObj, setSpriteObj] = useState<{
|
|
71
|
+
spriteTexture: THREE.Texture;
|
|
72
|
+
spriteData: SpriteData | null;
|
|
73
|
+
aspect: THREE.Vector3;
|
|
74
|
+
} | null>(null);
|
|
75
|
+
|
|
76
|
+
const calculateAspectRatio = useCallback((width: number, height: number, factor: number) => {
|
|
77
|
+
const viewport = viewportRef.current;
|
|
78
|
+
const adaptedHeight = height * (viewport.aspect > width / height ? viewport.width / width : viewport.height / height);
|
|
79
|
+
const adaptedWidth = width * (viewport.aspect > width / height ? viewport.width / width : viewport.height / height);
|
|
80
|
+
const scaleX = adaptedWidth * factor;
|
|
81
|
+
const scaleY = adaptedHeight * factor;
|
|
82
|
+
let finalMaxScaleW = Math.min(1, scaleX);
|
|
83
|
+
let finalMaxScaleH = Math.min(1, scaleY);
|
|
84
|
+
if (scaleX > 1) {
|
|
85
|
+
finalMaxScaleW = 1;
|
|
86
|
+
finalMaxScaleH = scaleY / scaleX;
|
|
87
|
+
}
|
|
88
|
+
return new THREE.Vector3(finalMaxScaleW, finalMaxScaleH, 1);
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
const getRowsAndColumns = useCallback((texture: THREE.Texture, frames: number) => {
|
|
92
|
+
if (!texture.image) return { rows: 0, columns: 0, frameWidth: 0, frameHeight: 0, emptyFrames: [] as SpriteMetaDimension[] };
|
|
93
|
+
const canvas = document.createElement('canvas');
|
|
94
|
+
const context = canvas.getContext('2d', canvasRenderingContext2DSettings);
|
|
95
|
+
if (!context) throw new Error('Failed to get 2d context');
|
|
96
|
+
canvas.width = texture.image.width;
|
|
97
|
+
canvas.height = texture.image.height;
|
|
98
|
+
context.drawImage(texture.image, 0, 0);
|
|
99
|
+
const width = texture.image.width;
|
|
100
|
+
const height = texture.image.height;
|
|
101
|
+
const columns = Math.round(Math.sqrt(frames * (width / height)));
|
|
102
|
+
const rows = Math.round(frames / columns);
|
|
103
|
+
const frameWidth = width / columns;
|
|
104
|
+
const frameHeight = height / rows;
|
|
105
|
+
const emptyFrames: SpriteMetaDimension[] = [];
|
|
106
|
+
for (let row = 0; row < rows; row++) {
|
|
107
|
+
for (let column = 0; column < columns; column++) {
|
|
108
|
+
const frameIndex = row * columns + column;
|
|
109
|
+
if (frameIndex >= frames) {
|
|
110
|
+
emptyFrames.push({ row, col: column });
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const data = context.getImageData(column * frameWidth, row * frameHeight, frameWidth, frameHeight).data;
|
|
114
|
+
if (checkIfFrameIsEmpty(data)) emptyFrames.push({ row, col: column });
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return { rows, columns, frameWidth, frameHeight, emptyFrames };
|
|
118
|
+
}, [canvasRenderingContext2DSettings]);
|
|
119
|
+
|
|
120
|
+
const calculateScaleRatio = useCallback((frames: FrameData[] | Record<string, FrameData[]>) => {
|
|
121
|
+
const process = (values: FrameData[]) => {
|
|
122
|
+
let largest: { area: number } | null = null;
|
|
123
|
+
for (const value of values) {
|
|
124
|
+
const area = value.frame.w * value.frame.h;
|
|
125
|
+
if (!largest || area > largest.area) largest = { area };
|
|
126
|
+
}
|
|
127
|
+
return values.map((value) => {
|
|
128
|
+
const area = value.frame.w * value.frame.h;
|
|
129
|
+
return { ...value, scaleRatio: largest ? (area === largest.area ? 1 : Math.sqrt(area / largest.area)) : 1 };
|
|
130
|
+
});
|
|
131
|
+
};
|
|
132
|
+
if (Array.isArray(frames)) return process(frames);
|
|
133
|
+
const result: Record<string, FrameData[]> = {};
|
|
134
|
+
for (const key in frames) result[key] = process(frames[key]);
|
|
135
|
+
return result;
|
|
136
|
+
}, []);
|
|
137
|
+
|
|
138
|
+
const parseFrames = useCallback((): Frames => {
|
|
139
|
+
const sprites: Record<string, FrameData[]> = {};
|
|
140
|
+
const data = spriteDataRef.current;
|
|
141
|
+
const delimiters = animationFramesRef.current;
|
|
142
|
+
if (!data) return [];
|
|
143
|
+
if (delimiters && Array.isArray(data.frames)) {
|
|
144
|
+
for (const delimiter of delimiters) {
|
|
145
|
+
sprites[delimiter] = [];
|
|
146
|
+
for (const rawValue of data.frames) {
|
|
147
|
+
const value = rawValue as SourceFrame;
|
|
148
|
+
if (typeof value.filename === 'string' && value.filename.toLowerCase().includes(delimiter.toLowerCase())) {
|
|
149
|
+
sprites[delimiter].push({ ...value, frame: value.frame, sourceSize: { w: value.sourceSize.w, h: value.sourceSize.h } });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
for (const key in sprites) sprites[key] = calculateScaleRatio(sprites[key]) as FrameData[];
|
|
154
|
+
return sprites;
|
|
155
|
+
}
|
|
156
|
+
if (delimiters && typeof data.frames === 'object') {
|
|
157
|
+
for (const delimiter of delimiters) {
|
|
158
|
+
sprites[delimiter] = [];
|
|
159
|
+
for (const innerKey in data.frames) {
|
|
160
|
+
const value = (data.frames as Record<string, FrameData[]>)[innerKey] as unknown as SourceFrame;
|
|
161
|
+
if (innerKey.toLowerCase().includes(delimiter.toLowerCase())) {
|
|
162
|
+
sprites[delimiter].push({ ...value, frame: value.frame, sourceSize: { w: value.sourceSize.w, h: value.sourceSize.h } });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
for (const key in sprites) sprites[key] = calculateScaleRatio(sprites[key]) as FrameData[];
|
|
167
|
+
return sprites;
|
|
168
|
+
}
|
|
169
|
+
const values = (Array.isArray(data.frames) ? data.frames : Object.values(data.frames).flat()).map((value) => ({
|
|
170
|
+
...value,
|
|
171
|
+
x: value.frame.x,
|
|
172
|
+
y: value.frame.y,
|
|
173
|
+
w: value.frame.w,
|
|
174
|
+
h: value.frame.h,
|
|
175
|
+
}));
|
|
176
|
+
return calculateScaleRatio(values) as FrameData[];
|
|
177
|
+
}, [calculateScaleRatio, spriteDataRef]);
|
|
178
|
+
|
|
179
|
+
const parseSpriteData = useCallback((jsonData: SpriteData | null, texture: THREE.Texture) => {
|
|
180
|
+
let aspect = new THREE.Vector3(1, 1, 1);
|
|
181
|
+
if (jsonData === null) {
|
|
182
|
+
if (texture && numberOfFrames) {
|
|
183
|
+
const width = texture.image.width;
|
|
184
|
+
const height = texture.image.height;
|
|
185
|
+
totalFrames.current = numberOfFrames;
|
|
186
|
+
const { rows, columns, frameWidth, frameHeight, emptyFrames } = getRowsAndColumns(texture, numberOfFrames);
|
|
187
|
+
const generated: SpriteData = { frames: [], meta: { version: '1.0', size: { w: width, h: height }, rows, columns, frameWidth, frameHeight, scale: '1' } };
|
|
188
|
+
for (let row = 0; row < rows; row++) {
|
|
189
|
+
for (let column = 0; column < columns; column++) {
|
|
190
|
+
if (emptyFrames.some((coord) => coord.row === row && coord.col === column)) continue;
|
|
191
|
+
(generated.frames as FrameData[]).push({
|
|
192
|
+
frame: { x: column * frameWidth, y: row * frameHeight, w: frameWidth, h: frameHeight },
|
|
193
|
+
scaleRatio: 1,
|
|
194
|
+
rotated: false,
|
|
195
|
+
trimmed: false,
|
|
196
|
+
spriteSourceSize: { x: 0, y: 0, w: frameWidth, h: frameHeight },
|
|
197
|
+
sourceSize: { w: frameWidth, h: frameHeight },
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
aspect = calculateAspectRatio(frameWidth, frameHeight, 0.1);
|
|
202
|
+
spriteDataRef.current = generated;
|
|
203
|
+
}
|
|
204
|
+
if (spriteDataRef.current?.frames) spriteDataRef.current.frames = calculateScaleRatio(spriteDataRef.current.frames);
|
|
205
|
+
} else if (texture) {
|
|
206
|
+
spriteDataRef.current = jsonData;
|
|
207
|
+
spriteDataRef.current.frames = parseFrames();
|
|
208
|
+
totalFrames.current = Array.isArray(jsonData.frames) ? jsonData.frames.length : Object.keys(jsonData.frames).length;
|
|
209
|
+
const { w, h } = getFirstFrame(jsonData.frames).sourceSize;
|
|
210
|
+
aspect = calculateAspectRatio(w, h, 0.1);
|
|
211
|
+
}
|
|
212
|
+
setSpriteData(spriteDataRef.current);
|
|
213
|
+
if ('encoding' in texture) (texture as THREE.Texture & { encoding: number }).encoding = 3001;
|
|
214
|
+
else texture.colorSpace = THREE.SRGBColorSpace;
|
|
215
|
+
setSpriteTexture(texture);
|
|
216
|
+
setSpriteObj({ spriteTexture: texture, spriteData: spriteDataRef.current, aspect });
|
|
217
|
+
}, [getRowsAndColumns, numberOfFrames, parseFrames, calculateAspectRatio, calculateScaleRatio]);
|
|
218
|
+
|
|
219
|
+
const loadJsonAndTextureAndExecuteCallback = useCallback((jsonUrl: string, textureUrl: string, callback: (data: SpriteData, texture: THREE.Texture) => void): void => {
|
|
220
|
+
const jsonPromise = fetch(jsonUrl).then((response) => response.json() as Promise<SpriteData>);
|
|
221
|
+
const texturePromise = new Promise<THREE.Texture>((resolve) => textureLoader.load(textureUrl, resolve));
|
|
222
|
+
Promise.all([jsonPromise, texturePromise]).then(([data, texture]) => callback(data, texture));
|
|
223
|
+
}, [textureLoader]);
|
|
224
|
+
|
|
225
|
+
const loadStandaloneSprite = useCallback((textureUrl?: string) => {
|
|
226
|
+
if (!textureUrl && !inputRef.current) throw new Error('Either textureUrl or input must be provided');
|
|
227
|
+
const validUrl = textureUrl ?? inputRef.current;
|
|
228
|
+
if (!validUrl) throw new Error('A valid texture URL must be provided');
|
|
229
|
+
textureLoader.load(validUrl, (texture) => parseSpriteData(null, texture));
|
|
230
|
+
}, [textureLoader, parseSpriteData]);
|
|
231
|
+
|
|
232
|
+
const loadJsonAndTexture = useCallback((textureUrl: string, jsonUrl?: string) => {
|
|
233
|
+
if (jsonUrl && textureUrl) loadJsonAndTextureAndExecuteCallback(jsonUrl, textureUrl, parseSpriteData);
|
|
234
|
+
else loadStandaloneSprite(textureUrl);
|
|
235
|
+
}, [loadJsonAndTextureAndExecuteCallback, loadStandaloneSprite, parseSpriteData]);
|
|
236
|
+
|
|
237
|
+
useLayoutEffect(() => {
|
|
238
|
+
if (jsonRef.current && inputRef.current) loadJsonAndTextureAndExecuteCallback(jsonRef.current, inputRef.current, parseSpriteData);
|
|
239
|
+
else if (inputRef.current) loadStandaloneSprite();
|
|
240
|
+
const currentInput = inputRef.current;
|
|
241
|
+
return () => { if (currentInput) useLoader.clear(THREE.TextureLoader, currentInput); };
|
|
242
|
+
}, [loadJsonAndTextureAndExecuteCallback, loadStandaloneSprite, parseSpriteData]);
|
|
243
|
+
|
|
244
|
+
useLayoutEffect(() => onLoad?.(spriteTexture, spriteData ?? null), [spriteTexture, spriteData, onLoad]);
|
|
245
|
+
return { spriteObj, loadJsonAndTexture };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
useSpriteLoader.preload = (url: string) => useLoader.preload(THREE.TextureLoader, url);
|
|
249
|
+
useSpriteLoader.clear = (input: string) => useLoader.clear(THREE.TextureLoader, input);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type Hls from 'hls.js';
|
|
2
|
+
|
|
3
|
+
export const IS_BROWSER =
|
|
4
|
+
typeof window !== 'undefined' &&
|
|
5
|
+
typeof window.document?.createElement === 'function' &&
|
|
6
|
+
typeof window.navigator?.userAgent === 'string';
|
|
7
|
+
|
|
8
|
+
let hlsModule: typeof import('hls.js') | null = null;
|
|
9
|
+
|
|
10
|
+
export async function getHls(...args: ConstructorParameters<typeof Hls>): Promise<Hls | null> {
|
|
11
|
+
hlsModule ??= await import('hls.js');
|
|
12
|
+
const Ctor = hlsModule.default;
|
|
13
|
+
return Ctor.isSupported() ? new Ctor(...args) : null;
|
|
14
|
+
}
|
package/src/index.ts
ADDED