@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,104 @@
1
+ import { type ThreeRef, useFrame, useThree } from '@octanejs/three';
2
+ import {
3
+ useEffect,
4
+ useImperativeHandle,
5
+ useMemo,
6
+ useRef,
7
+ useState,
8
+ } from '@octanejs/three/renderer';
9
+ import { Euler, Vector3 } from 'three';
10
+ import { SimplexNoise } from 'three-stdlib';
11
+
12
+ function assignRef<T>(ref: ThreeRef<T> | undefined, value: T | null): void {
13
+ if (Array.isArray(ref)) for (const item of ref) assignRef(item, value);
14
+ else if (typeof ref === 'function') ref(value);
15
+ else if (ref && 'current' in ref) ref.current = value;
16
+ }
17
+
18
+ export interface ShakeController {
19
+ getIntensity: () => number;
20
+ setIntensity: (value: number) => void;
21
+ }
22
+
23
+ type ControlsProto = {
24
+ update(): void;
25
+ target: Vector3;
26
+ addEventListener(event: string, callback: (event: any) => void): void;
27
+ removeEventListener(event: string, callback: (event: any) => void): void;
28
+ };
29
+
30
+ export interface CameraShakeProps {
31
+ intensity?: number;
32
+ decay?: boolean;
33
+ decayRate?: number;
34
+ maxYaw?: number;
35
+ maxPitch?: number;
36
+ maxRoll?: number;
37
+ yawFrequency?: number;
38
+ pitchFrequency?: number;
39
+ rollFrequency?: number;
40
+ }
41
+
42
+ export function CameraShake({
43
+ intensity = 1,
44
+ decay,
45
+ decayRate = 0.65,
46
+ maxYaw = 0.1,
47
+ maxPitch = 0.1,
48
+ maxRoll = 0.1,
49
+ yawFrequency = 0.1,
50
+ pitchFrequency = 0.1,
51
+ rollFrequency = 0.1,
52
+ ref,
53
+ }: CameraShakeProps & { ref?: ThreeRef<ShakeController | undefined> }) @{
54
+ const camera = useThree((state) => state.camera);
55
+ const defaultControls = useThree((state) => state.controls) as unknown as ControlsProto;
56
+ const intensityRef = useRef(intensity);
57
+ const initialRotation = useRef<Euler>(camera.rotation.clone());
58
+ const [yawNoise] = useState(() => new SimplexNoise());
59
+ const [pitchNoise] = useState(() => new SimplexNoise());
60
+ const [rollNoise] = useState(() => new SimplexNoise());
61
+ const imperativeRef = useMemo(
62
+ () => (value: ShakeController | null | undefined) => assignRef(ref, value ?? null),
63
+ [ref],
64
+ );
65
+ const constrainIntensity = () => {
66
+ if (intensityRef.current < 0 || intensityRef.current > 1) intensityRef.current =
67
+ intensityRef.current < 0 ? 0 : 1;
68
+ };
69
+ useImperativeHandle(
70
+ imperativeRef,
71
+ () => ({
72
+ getIntensity: () => intensityRef.current,
73
+ setIntensity: (value: number) => {
74
+ intensityRef.current = value;
75
+ constrainIntensity();
76
+ },
77
+ }),
78
+ [],
79
+ );
80
+ useEffect(() => {
81
+ if (defaultControls) {
82
+ const callback = () => void (initialRotation.current = camera.rotation.clone());
83
+ defaultControls.addEventListener('change', callback);
84
+ callback();
85
+ return () => void defaultControls.removeEventListener('change', callback);
86
+ }
87
+ }, [camera, defaultControls]);
88
+ useFrame((state, delta) => {
89
+ const shake = Math.pow(intensityRef.current, 2);
90
+ const yaw = maxYaw * shake * yawNoise.noise(state.clock.elapsedTime * yawFrequency, 1);
91
+ const pitch = maxPitch * shake * pitchNoise.noise(state.clock.elapsedTime * pitchFrequency, 1);
92
+ const roll = maxRoll * shake * rollNoise.noise(state.clock.elapsedTime * rollFrequency, 1);
93
+ camera.rotation.set(
94
+ initialRotation.current.x + pitch,
95
+ initialRotation.current.y + yaw,
96
+ initialRotation.current.z + roll,
97
+ );
98
+ if (decay && intensityRef.current > 0) {
99
+ intensityRef.current -= decayRate * delta;
100
+ constrainIntensity();
101
+ }
102
+ });
103
+ null;
104
+ }
@@ -0,0 +1,107 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { extend, type ThreeElements, type ThreeRef, useFrame, useThree } from '@octanejs/three';
3
+ import { useImperativeHandle, useLayoutEffect, useRef, useState, type RefObject } from '@octanejs/three/renderer';
4
+ import type { OctaneNode } from 'octane';
5
+ import * as THREE from 'three';
6
+ import { FullScreenQuad } from 'three-stdlib';
7
+ import { CausticsMaterial, type CausticsMaterialType, CausticsProjectionMaterial, type CausticsProjectionMaterialType, createNormalMaterial } from '../materials/CausticsMaterials.js';
8
+ import { useFBO } from './Fbo.three.tsrx';
9
+ import { useHelper } from './Helper.three.tsrx';
10
+ import { Edges } from './lines.three.tsrx';
11
+
12
+ export type CausticsProps = Omit<ThreeElements['group'], 'ref'> & {
13
+ frames?: number; debug?: boolean; causticsOnly: boolean; backside: boolean; ior?: number; backsideIOR?: number;
14
+ worldRadius?: number; intensity?: number; color?: THREE.ColorRepresentation; resolution?: number;
15
+ lightSource?: [x: number, y: number, z: number] | RefObject<THREE.Object3D>; children?: OctaneNode;
16
+ };
17
+
18
+ const CausticsProjectionMaterialElement = extend(CausticsProjectionMaterial);
19
+ const NORMALPROPS = { depth: true, minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, type: THREE.UnsignedByteType };
20
+ const CAUSTICPROPS = { minFilter: THREE.LinearMipmapLinearFilter, magFilter: THREE.LinearFilter, type: THREE.FloatType, generateMipmaps: true };
21
+
22
+ export function Caustics({
23
+ debug, children, frames = 1, ior = 1.1, color = 'white', causticsOnly = false, backside = false,
24
+ backsideIOR = 1.1, worldRadius = 0.3125, intensity = 0.05, resolution = 2024,
25
+ lightSource = [5, 5, 5], ref: forwardedRef, ...props
26
+ }: CausticsProps & { ref?: ThreeRef<THREE.Group> }) @{
27
+ const ref = useRef<THREE.Group | null>(null);
28
+ const camera = useRef<THREE.OrthographicCamera | null>(null);
29
+ const scene = useRef<THREE.Scene | null>(null);
30
+ const plane = useRef<THREE.Mesh<THREE.PlaneGeometry, CausticsProjectionMaterialType> | null>(null);
31
+ const gl = useThree((state) => state.gl);
32
+ const helper = useHelper((debug && camera) as any, THREE.CameraHelper);
33
+ const normalTarget = useFBO(resolution, resolution, NORMALPROPS);
34
+ const normalTargetB = useFBO(resolution, resolution, NORMALPROPS);
35
+ const causticsTarget = useFBO(resolution, resolution, CAUSTICPROPS);
36
+ const causticsTargetB = useFBO(resolution, resolution, CAUSTICPROPS);
37
+ const [normalMat] = useState(() => createNormalMaterial());
38
+ const [normalMatB] = useState(() => createNormalMaterial(THREE.BackSide));
39
+ const [causticsMaterial] = useState(() => new CausticsMaterial() as CausticsMaterialType);
40
+ const [causticsQuad] = useState(() => new FullScreenQuad(causticsMaterial as any));
41
+ useLayoutEffect(() => { ref.current!.updateWorldMatrix(false, true); });
42
+ let count = 0;
43
+ const v = new THREE.Vector3(); const lpF = new THREE.Frustum(); const lpM = new THREE.Matrix4(); const lpP = new THREE.Plane();
44
+ const lightDir = new THREE.Vector3(); const lightDirInv = new THREE.Vector3(); const bounds = new THREE.Box3(); const focusPos = new THREE.Vector3();
45
+ const boundsVertices: THREE.Vector3[] = []; const worldVerts: THREE.Vector3[] = []; const projectedVerts: THREE.Vector3[] = []; const lightDirs: THREE.Vector3[] = [];
46
+ const cameraPos = new THREE.Vector3();
47
+ for (let index = 0; index < 8; index++) { boundsVertices.push(new THREE.Vector3()); worldVerts.push(new THREE.Vector3()); projectedVerts.push(new THREE.Vector3()); lightDirs.push(new THREE.Vector3()); }
48
+ useFrame(() => {
49
+ if (frames !== Infinity && count++ >= frames) return;
50
+ if (Array.isArray(lightSource)) lightDir.fromArray(lightSource).normalize();
51
+ else lightDir.copy(ref.current!.worldToLocal(lightSource.current!.getWorldPosition(v)).normalize());
52
+ lightDirInv.copy(lightDir).multiplyScalar(-1);
53
+ scene.current!.parent?.matrixWorld.identity();
54
+ bounds.setFromObject(scene.current!, true);
55
+ boundsVertices[0].set(bounds.min.x, bounds.min.y, bounds.min.z); boundsVertices[1].set(bounds.min.x, bounds.min.y, bounds.max.z);
56
+ boundsVertices[2].set(bounds.min.x, bounds.max.y, bounds.min.z); boundsVertices[3].set(bounds.min.x, bounds.max.y, bounds.max.z);
57
+ boundsVertices[4].set(bounds.max.x, bounds.min.y, bounds.min.z); boundsVertices[5].set(bounds.max.x, bounds.min.y, bounds.max.z);
58
+ boundsVertices[6].set(bounds.max.x, bounds.max.y, bounds.min.z); boundsVertices[7].set(bounds.max.x, bounds.max.y, bounds.max.z);
59
+ for (let index = 0; index < 8; index++) worldVerts[index].copy(boundsVertices[index]);
60
+ bounds.getCenter(focusPos); boundsVertices.forEach((point) => point.sub(focusPos));
61
+ const lightPlane = lpP.set(lightDirInv, 0);
62
+ boundsVertices.map((point, index) => lightPlane.projectPoint(point, projectedVerts[index]));
63
+ const centralVert = projectedVerts.reduce((a, b) => a.add(b), v.set(0, 0, 0)).divideScalar(projectedVerts.length);
64
+ const radius = projectedVerts.map((point) => point.distanceTo(centralVert)).reduce((a, b) => Math.max(a, b));
65
+ const dirLength = boundsVertices.map((point) => point.dot(lightDir)).reduce((a, b) => Math.max(a, b));
66
+ camera.current!.position.copy(cameraPos.copy(lightDir).multiplyScalar(dirLength).add(focusPos));
67
+ camera.current!.lookAt(scene.current!.localToWorld(focusPos));
68
+ const dirMatrix = lpM.lookAt(camera.current!.position, focusPos, v.set(0, 1, 0));
69
+ camera.current!.left = -radius; camera.current!.right = radius; camera.current!.top = radius; camera.current!.bottom = -radius;
70
+ const yOffset = v.set(0, radius, 0).applyMatrix4(dirMatrix); const yTime = (camera.current!.position.y + yOffset.y) / lightDir.y;
71
+ camera.current!.near = 0.1; camera.current!.far = yTime; camera.current!.updateProjectionMatrix(); camera.current!.updateMatrixWorld();
72
+ const groundProjectedCoords = worldVerts.map((point, index) => point.add(lightDirs[index].copy(lightDir).multiplyScalar(-point.y / lightDir.y)));
73
+ const centerPos = groundProjectedCoords.reduce((a, b) => a.add(b), v.set(0, 0, 0)).divideScalar(groundProjectedCoords.length);
74
+ const maxSize = 2 * groundProjectedCoords.map((point) => Math.hypot(point.x - centerPos.x, point.z - centerPos.z)).reduce((a, b) => Math.max(a, b));
75
+ plane.current!.scale.setScalar(maxSize); plane.current!.position.copy(centerPos);
76
+ if (debug) (helper.current as THREE.CameraHelper | null)?.update?.();
77
+ normalMatB.viewMatrix.value = normalMat.viewMatrix.value = camera.current!.matrixWorldInverse;
78
+ const dirLightNearPlane = lpF.setFromProjectionMatrix(lpM.multiplyMatrices(camera.current!.projectionMatrix, camera.current!.matrixWorldInverse)).planes[4];
79
+ causticsMaterial.cameraMatrixWorld = camera.current!.matrixWorld; causticsMaterial.cameraProjectionMatrixInv = camera.current!.projectionMatrixInverse;
80
+ causticsMaterial.lightDir = lightDirInv; causticsMaterial.lightPlaneNormal = dirLightNearPlane.normal; causticsMaterial.lightPlaneConstant = dirLightNearPlane.constant;
81
+ causticsMaterial.near = camera.current!.near; causticsMaterial.far = camera.current!.far; causticsMaterial.resolution = resolution;
82
+ causticsMaterial.size = radius; causticsMaterial.intensity = intensity; causticsMaterial.worldRadius = worldRadius;
83
+ scene.current!.visible = true;
84
+ gl.setRenderTarget(normalTarget); gl.clear(); scene.current!.overrideMaterial = normalMat; gl.render(scene.current!, camera.current!);
85
+ gl.setRenderTarget(normalTargetB); gl.clear();
86
+ if (backside) { scene.current!.overrideMaterial = normalMatB; gl.render(scene.current!, camera.current!); }
87
+ scene.current!.overrideMaterial = null;
88
+ causticsMaterial.ior = ior; plane.current!.material.lightProjMatrix = camera.current!.projectionMatrix; plane.current!.material.lightViewMatrix = camera.current!.matrixWorldInverse;
89
+ causticsMaterial.normalTexture = normalTarget.texture; causticsMaterial.depthTexture = normalTarget.depthTexture;
90
+ gl.setRenderTarget(causticsTarget); gl.clear(); causticsQuad.render(gl as any);
91
+ causticsMaterial.ior = backsideIOR; causticsMaterial.normalTexture = normalTargetB.texture; causticsMaterial.depthTexture = normalTargetB.depthTexture;
92
+ gl.setRenderTarget(causticsTargetB); gl.clear(); if (backside) causticsQuad.render(gl as any);
93
+ gl.setRenderTarget(null); if (causticsOnly) scene.current!.visible = false;
94
+ });
95
+ useImperativeHandle((forwardedRef ?? null) as any, () => ref.current!, []);
96
+ <group ref={ref} {...props}>
97
+ <scene ref={scene}>
98
+ <orthographicCamera ref={camera} up={[0, 1, 0]} />
99
+ {children}
100
+ </scene>
101
+ <mesh renderOrder={2} ref={plane} rotation-x={-Math.PI / 2}>
102
+ <planeGeometry />
103
+ <CausticsProjectionMaterialElement transparent color={color} causticsTexture={causticsTarget.texture as any} causticsTextureB={causticsTargetB.texture as any} blending={THREE.CustomBlending} blendSrc={THREE.OneFactor} blendDst={THREE.SrcAlphaFactor} depthWrite={false} />
104
+ @if (debug) { <Edges><lineBasicMaterial color="#ffff00" toneMapped={false} /></Edges> }
105
+ </mesh>
106
+ </group>
107
+ }
@@ -0,0 +1,145 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import { type ThreeElements, type ThreeRef } from '@octanejs/three';
3
+ import { useLayoutEffect, useMemo, useRef, useState } from '@octanejs/three/renderer';
4
+ import { Box3, Group, type Object3D, Sphere, Vector3 } from 'three';
5
+
6
+ function assignRef<T>(ref: ThreeRef<T> | undefined, value: T | null): void {
7
+ if (Array.isArray(ref)) for (const item of ref) assignRef(item, value);
8
+ else if (typeof ref === 'function') ref(value);
9
+ else if (ref && 'current' in ref) ref.current = value;
10
+ }
11
+
12
+ export type OnCenterCallbackProps = {
13
+ parent: Object3D;
14
+ container: Object3D;
15
+ width: number;
16
+ height: number;
17
+ depth: number;
18
+ boundingBox: Box3;
19
+ boundingSphere: Sphere;
20
+ center: Vector3;
21
+ verticalAlignment: number;
22
+ horizontalAlignment: number;
23
+ depthAlignment: number;
24
+ };
25
+
26
+ export type CenterProps =
27
+ Omit<ThreeElements['group'], 'ref'> & {
28
+ top?: boolean;
29
+ right?: boolean;
30
+ bottom?: boolean;
31
+ left?: boolean;
32
+ front?: boolean;
33
+ back?: boolean;
34
+ disable?: boolean;
35
+ disableX?: boolean;
36
+ disableY?: boolean;
37
+ disableZ?: boolean;
38
+ object?: Object3D | null;
39
+ precise?: boolean;
40
+ onCentered?: (props: OnCenterCallbackProps) => void;
41
+ cacheKey?: any;
42
+ };
43
+
44
+ export function Center({
45
+ children,
46
+ object,
47
+ disable,
48
+ disableX,
49
+ disableY,
50
+ disableZ,
51
+ left,
52
+ right,
53
+ top,
54
+ bottom,
55
+ front,
56
+ back,
57
+ onCentered,
58
+ precise = true,
59
+ cacheKey = 0,
60
+ ref,
61
+ ...props
62
+ }: CenterProps & { ref?: ThreeRef<Group> }) @{
63
+ const container = useRef<Group | null>(null);
64
+ const outer = useRef<Group | null>(null);
65
+ const inner = useRef<Group | null>(null);
66
+ const [box] = useState(() => new Box3());
67
+ const [center] = useState(() => new Vector3());
68
+ const [sphere] = useState(() => new Sphere());
69
+ const mergedRef = useMemo(
70
+ () => (value: Group | null) => {
71
+ container.current = value;
72
+ assignRef(ref, value);
73
+ },
74
+ [ref],
75
+ );
76
+ useLayoutEffect(() => {
77
+ if (!outer.current || !inner.current || !container.current) return;
78
+ outer.current.matrixWorld.identity();
79
+ box.setFromObject(object ?? inner.current, precise);
80
+ const width = box.max.x - box.min.x;
81
+ const height = box.max.y - box.min.y;
82
+ const depth = box.max.z - box.min.z;
83
+ box.getCenter(center);
84
+ box.getBoundingSphere(sphere);
85
+ const verticalAlignment =
86
+ top
87
+ ? height / 2
88
+ : bottom
89
+ ? -height / 2
90
+ : 0;
91
+ const horizontalAlignment =
92
+ left
93
+ ? -width / 2
94
+ : right
95
+ ? width / 2
96
+ : 0;
97
+ const depthAlignment =
98
+ front
99
+ ? depth / 2
100
+ : back
101
+ ? -depth / 2
102
+ : 0;
103
+ outer.current.position.set(
104
+ disable || disableX ? 0 : -center.x + horizontalAlignment,
105
+ disable || disableY ? 0 : -center.y + verticalAlignment,
106
+ disable || disableZ ? 0 : -center.z + depthAlignment,
107
+ );
108
+ onCentered?.({
109
+ parent: container.current.parent!,
110
+ container: container.current,
111
+ width,
112
+ height,
113
+ depth,
114
+ boundingBox: box,
115
+ boundingSphere: sphere,
116
+ center,
117
+ verticalAlignment,
118
+ horizontalAlignment,
119
+ depthAlignment,
120
+ });
121
+ }, [
122
+ cacheKey,
123
+ onCentered,
124
+ top,
125
+ left,
126
+ front,
127
+ disable,
128
+ disableX,
129
+ disableY,
130
+ disableZ,
131
+ object,
132
+ precise,
133
+ right,
134
+ bottom,
135
+ back,
136
+ box,
137
+ center,
138
+ sphere,
139
+ ]);
140
+ <group ref={mergedRef} {...props}>
141
+ <group ref={outer}>
142
+ <group ref={inner}>{children}</group>
143
+ </group>
144
+ </group>
145
+ }
@@ -0,0 +1,167 @@
1
+ /** @jsxImportSource @octanejs/three/intrinsics */
2
+ import type { ThreeElements, ThreeRef } from '@octanejs/three';
3
+ import { useMemo } from '@octanejs/three/renderer';
4
+ import type { OctaneNode } from 'octane';
5
+ import * as THREE from 'three';
6
+ import { SkeletonUtils } from 'three-stdlib';
7
+ import { isOctaneElement } from './octane-element.js';
8
+
9
+ export type CloneProps =
10
+ Omit<ThreeElements['group'], 'ref' | 'children'> & {
11
+ object: THREE.Object3D | THREE.Object3D[];
12
+ children?: OctaneNode;
13
+ deep?: boolean | 'materialsOnly' | 'geometriesOnly';
14
+ keys?: string[];
15
+ inject?: ThreeElements['mesh'] | OctaneNode | ((object: THREE.Object3D) => OctaneNode);
16
+ castShadow?: boolean;
17
+ receiveShadow?: boolean;
18
+ isChild?: boolean;
19
+ };
20
+
21
+ interface CloneConfig {
22
+ deep?: CloneProps['deep'];
23
+ keys?: string[];
24
+ inject?: CloneProps['inject'];
25
+ castShadow?: boolean;
26
+ receiveShadow?: boolean;
27
+ }
28
+
29
+ const defaultKeys = [
30
+ 'near',
31
+ 'far',
32
+ 'color',
33
+ 'distance',
34
+ 'decay',
35
+ 'penumbra',
36
+ 'angle',
37
+ 'intensity',
38
+ 'skeleton',
39
+ 'visible',
40
+ 'castShadow',
41
+ 'receiveShadow',
42
+ 'morphTargetDictionary',
43
+ 'morphTargetInfluences',
44
+ 'name',
45
+ 'geometry',
46
+ 'material',
47
+ 'position',
48
+ 'rotation',
49
+ 'scale',
50
+ 'up',
51
+ 'userData',
52
+ 'bindMode',
53
+ 'bindMatrix',
54
+ 'bindMatrixInverse',
55
+ 'skeleton',
56
+ ];
57
+
58
+ function createSpread(child: THREE.Object3D, config: CloneConfig) {
59
+ const keys = config.keys ?? defaultKeys;
60
+ let spread: Record<string, any> = {};
61
+ for (const key of keys) spread[key] = (child as any)[key];
62
+ if (config.deep) {
63
+ if (spread.geometry && config.deep !== 'materialsOnly') spread.geometry =
64
+ spread.geometry.clone();
65
+ if (spread.material && config.deep !== 'geometriesOnly') spread.material =
66
+ spread.material.clone();
67
+ }
68
+ let injectChildren: OctaneNode;
69
+ if (config.inject) {
70
+ if (typeof config.inject === 'function') injectChildren = config.inject(child);
71
+ else if (isOctaneElement(config.inject)) injectChildren = config.inject;
72
+ else spread = { ...spread, ...config.inject };
73
+ }
74
+ if (child instanceof THREE.Mesh) {
75
+ if (config.castShadow) spread.castShadow = true;
76
+ if (config.receiveShadow) spread.receiveShadow = true;
77
+ }
78
+ return { spread, injectChildren };
79
+ }
80
+
81
+ function assignRef<T>(ref: ThreeRef<T> | undefined, value: T | null): void {
82
+ if (Array.isArray(ref)) for (const item of ref) assignRef(item, value);
83
+ else if (typeof ref === 'function') ref(value);
84
+ else if (ref !== undefined && 'current' in ref) ref.current = value;
85
+ }
86
+
87
+ interface CloneNodeProps extends CloneConfig {
88
+ object: THREE.Object3D;
89
+ children?: OctaneNode;
90
+ ref?: ThreeRef<THREE.Object3D>;
91
+ rootProps?: Record<string, unknown>;
92
+ }
93
+
94
+ function createShell(object: THREE.Object3D): THREE.Object3D {
95
+ const Constructor = object.constructor as new () => THREE.Object3D;
96
+ return new Constructor();
97
+ }
98
+
99
+ function CloneNode({ object, children, ref, rootProps, ...config }: CloneNodeProps) @{
100
+ const shell = useMemo(() => createShell(object), [object]);
101
+ const { spread, injectChildren } = useMemo(() => createSpread(object, config), [
102
+ object,
103
+ config.keys,
104
+ config.deep,
105
+ config.inject,
106
+ config.castShadow,
107
+ config.receiveShadow,
108
+ ]);
109
+ const objectRef = useMemo(() => (value: THREE.Object3D | null) => assignRef(ref, value), [ref]);
110
+
111
+ <primitive object={shell} {...spread} {...rootProps} ref={objectRef}>
112
+ @for (const child of object.children; key child.uuid) {
113
+ @if (child.type === 'Bone') {
114
+ <primitive object={child} />
115
+ } @else {
116
+ <CloneNode object={child} {...config} />
117
+ }
118
+ }
119
+ {children}
120
+ {injectChildren}
121
+ </primitive>
122
+ }
123
+
124
+ export function Clone({
125
+ isChild = false,
126
+ object,
127
+ children,
128
+ deep,
129
+ castShadow,
130
+ receiveShadow,
131
+ inject,
132
+ keys,
133
+ ref,
134
+ ...props
135
+ }: CloneProps & { ref?: ThreeRef<THREE.Group> }) @{
136
+ const resolvedObject = useMemo(() => {
137
+ if (isChild || Array.isArray(object)) return object;
138
+ let isSkinned = false;
139
+ object.traverse((child) => {
140
+ if ((child as THREE.SkinnedMesh).isSkinnedMesh) isSkinned = true;
141
+ });
142
+ return isSkinned
143
+ ? SkeletonUtils.clone(
144
+ object as unknown as Parameters<typeof SkeletonUtils.clone>[0],
145
+ ) as unknown as THREE.Object3D
146
+ : object;
147
+ }, [object, isChild]);
148
+ const config = { deep, castShadow, receiveShadow, inject, keys };
149
+ const groupRef = useMemo(() => (value: THREE.Group | null) => assignRef(ref, value), [ref]);
150
+
151
+ @if (Array.isArray(resolvedObject)) {
152
+ <group {...props} ref={groupRef}>
153
+ @for (const child of resolvedObject; key child.uuid) {
154
+ <CloneNode object={child} {...config} />
155
+ }
156
+ {children}
157
+ </group>
158
+ } @else {
159
+ <CloneNode
160
+ object={resolvedObject}
161
+ children={children}
162
+ ref={ref as ThreeRef<THREE.Object3D> | undefined}
163
+ rootProps={props}
164
+ {...config}
165
+ />
166
+ }
167
+ }