mbt-3d 0.1.0 → 0.1.2
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/README.md +41 -0
- package/dist/index.d.ts +8 -40
- package/dist/mbt-3d.cjs +2 -0
- package/dist/mbt-3d.cjs.map +1 -0
- package/dist/mbt-3d.js +404 -0
- package/dist/mbt-3d.js.map +1 -0
- package/package.json +26 -26
- package/dist/mbt-3d.cjs.js +0 -2
- package/dist/mbt-3d.cjs.js.map +0 -1
- package/dist/mbt-3d.es.js +0 -420
- package/dist/mbt-3d.es.js.map +0 -1
package/README.md
CHANGED
|
@@ -20,3 +20,44 @@ npm install react react-dom three @react-three/fiber @react-three/drei
|
|
|
20
20
|
- Three.js >= 0.150.0
|
|
21
21
|
- @react-three/fiber >= 8.0.0
|
|
22
22
|
- @react-three/drei >= 9.0.0
|
|
23
|
+
|
|
24
|
+
## Local Development
|
|
25
|
+
|
|
26
|
+
For local development and testing with another project:
|
|
27
|
+
|
|
28
|
+
**1. Link the library:**
|
|
29
|
+
```bash
|
|
30
|
+
# In mbt-3d directory
|
|
31
|
+
npm link
|
|
32
|
+
|
|
33
|
+
# Start watch mode for auto-rebuild on changes
|
|
34
|
+
npm run build:lib:watch
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
**2. Use in your project:**
|
|
38
|
+
```bash
|
|
39
|
+
# In your project directory
|
|
40
|
+
npm link mbt-3d
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**3. When done, unlink:**
|
|
44
|
+
```bash
|
|
45
|
+
# In your project directory
|
|
46
|
+
npm unlink mbt-3d
|
|
47
|
+
|
|
48
|
+
# In mbt-3d directory
|
|
49
|
+
npm unlink
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**Alternative: Use file: protocol**
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"mbt-3d": "file:../path/to/mbt-3d"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## License
|
|
62
|
+
|
|
63
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ForwardRefExoticComponent } from 'react';
|
|
2
|
-
import { JSX } from 'react/jsx-runtime';
|
|
3
|
-
import { JSX as JSX_2 } from 'react';
|
|
2
|
+
import { JSX as JSX_2 } from 'react/jsx-runtime';
|
|
4
3
|
import { RefAttributes } from 'react';
|
|
5
4
|
import * as THREE from 'three';
|
|
6
5
|
|
|
@@ -177,42 +176,17 @@ export declare interface AnimatedModelProps extends Omit<ModelProps, 'onLoad'> {
|
|
|
177
176
|
* </AnimatedModel>
|
|
178
177
|
* ```
|
|
179
178
|
*/
|
|
180
|
-
export declare function BoneAttachment({ children, bone, position, rotation, scale, }: BoneAttachmentProps):
|
|
179
|
+
export declare function BoneAttachment({ children, bone, position, rotation, scale, }: BoneAttachmentProps): JSX.Element | null;
|
|
181
180
|
|
|
182
|
-
/**
|
|
183
|
-
* Props for BoneAttachment component
|
|
184
|
-
*
|
|
185
|
-
* @remarks
|
|
186
|
-
* Must be used as a child of AnimatedModel
|
|
187
|
-
*
|
|
188
|
-
* @example
|
|
189
|
-
* ```tsx
|
|
190
|
-
* <AnimatedModel url="/character.glb">
|
|
191
|
-
* <BoneAttachment
|
|
192
|
-
* bone="hand_r"
|
|
193
|
-
* position={[0.1, 0, 0]}
|
|
194
|
-
* rotation={[0, Math.PI/2, 0]}
|
|
195
|
-
* >
|
|
196
|
-
* <Model url="/sword.glb" />
|
|
197
|
-
* </BoneAttachment>
|
|
198
|
-
* </AnimatedModel>
|
|
199
|
-
* ```
|
|
200
|
-
*/
|
|
201
181
|
export declare interface BoneAttachmentProps {
|
|
202
|
-
/** Child components to attach to the bone */
|
|
203
182
|
children: React.ReactNode;
|
|
204
|
-
/**
|
|
205
|
-
* Bone name to attach to. Common names:
|
|
206
|
-
* - Hands: "hand_r", "hand_l", "DEF-handR", "DEF-handL"
|
|
207
|
-
* - Spine/Back: "spine", "spine_upper", "back"
|
|
208
|
-
* - Head: "head", "neck"
|
|
209
|
-
*/
|
|
183
|
+
/** Bone name to attach to */
|
|
210
184
|
bone: string;
|
|
211
|
-
/** Position offset relative to
|
|
185
|
+
/** Position offset relative to bone */
|
|
212
186
|
position?: [number, number, number];
|
|
213
|
-
/** Rotation offset relative to
|
|
187
|
+
/** Rotation offset relative to bone (radians) */
|
|
214
188
|
rotation?: [number, number, number];
|
|
215
|
-
/**
|
|
189
|
+
/** Scale */
|
|
216
190
|
scale?: number | [number, number, number];
|
|
217
191
|
}
|
|
218
192
|
|
|
@@ -245,7 +219,7 @@ export declare interface BoneAttachmentProps {
|
|
|
245
219
|
* />
|
|
246
220
|
* ```
|
|
247
221
|
*/
|
|
248
|
-
export declare function Model({ url, position, rotation, scale, onLoad, onError, }: ModelProps):
|
|
222
|
+
export declare function Model({ url, position, rotation, scale, onLoad, onError: _onError, }: ModelProps): JSX_2.Element;
|
|
249
223
|
|
|
250
224
|
export declare namespace Model {
|
|
251
225
|
var preload: (url: string) => void;
|
|
@@ -432,7 +406,7 @@ export declare function preloadModel(url: string): void;
|
|
|
432
406
|
* </Scene3D>
|
|
433
407
|
* ```
|
|
434
408
|
*/
|
|
435
|
-
export declare function Scene3D({ children, camera, controls, background, shadows, ambientIntensity, spotLight, contactShadows, style, className, }: Scene3DProps):
|
|
409
|
+
export declare function Scene3D({ children, camera, controls, background, shadows, ambientIntensity, spotLight, contactShadows, style, className, }: Scene3DProps): JSX_2.Element;
|
|
436
410
|
|
|
437
411
|
/**
|
|
438
412
|
* Props for Scene3D component
|
|
@@ -548,9 +522,3 @@ export declare function useMorphTargets(scene: THREE.Object3D, initialValues?: R
|
|
|
548
522
|
};
|
|
549
523
|
|
|
550
524
|
export { }
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
export declare namespace Model {
|
|
554
|
-
var preload: (url: string) => void;
|
|
555
|
-
}
|
|
556
|
-
|
package/dist/mbt-3d.cjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const o=require("react"),x=require("@react-three/drei"),Z=require("three"),W=require("three/examples/jsm/utils/SkeletonUtils.js"),D=require("@react-three/fiber"),b=require("react/jsx-runtime");function G(e){const r=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(e){for(const s in e)if(s!=="default"){const n=Object.getOwnPropertyDescriptor(e,s);Object.defineProperty(r,s,n.get?n:{enumerable:!0,get:()=>e[s]})}}return r.default=e,Object.freeze(r)}const w=G(Z),_=G(W);function $({background:e}){const r=e==null?void 0:e.startsWith("#");return e?r?b.jsx("color",{attach:"background",args:[e]}):b.jsx(U,{url:e}):null}function U({url:e}){const r=x.useTexture(e);return o.useMemo(()=>{r.colorSpace=w.SRGBColorSpace},[r]),b.jsx("primitive",{attach:"background",object:r})}function z({children:e,camera:r={},controls:s={},background:n,shadows:i=!0,ambientIntensity:f=.5,spotLight:u={},contactShadows:y=!0,style:d,className:l}){const t={position:r.position||[0,2,5],fov:r.fov||45},a={enabled:s.enabled??!0,enablePan:s.enablePan??!0,enableZoom:s.enableZoom??!0,enableRotate:s.enableRotate??!0,minDistance:s.minDistance,maxDistance:s.maxDistance,minPolarAngle:s.minPolarAngle,maxPolarAngle:s.maxPolarAngle,autoRotate:s.autoRotate??!1,autoRotateSpeed:s.autoRotateSpeed??2},m={position:u.position||[5,10,5],intensity:u.intensity??50,castShadow:u.castShadow??!0},p=typeof y=="object"?{position:y.position||[0,-1,0],opacity:y.opacity??.5,blur:y.blur??2}:y?{position:[0,-1,0],opacity:.5,blur:2}:null;return b.jsx("div",{style:d,className:l,children:b.jsxs(D.Canvas,{shadows:i,camera:{position:t.position,fov:t.fov},style:{width:"100%",height:"100%"},children:[b.jsx(o.Suspense,{fallback:null,children:b.jsx($,{background:n})}),b.jsx("ambientLight",{intensity:f}),b.jsx("spotLight",{position:m.position,intensity:m.intensity,castShadow:m.castShadow}),b.jsx(o.Suspense,{fallback:null,children:e}),b.jsx(x.OrbitControls,{makeDefault:!0,enabled:a.enabled,enablePan:a.enablePan,enableZoom:a.enableZoom,enableRotate:a.enableRotate,minDistance:a.minDistance,maxDistance:a.maxDistance,minPolarAngle:a.minPolarAngle,maxPolarAngle:a.maxPolarAngle,autoRotate:a.autoRotate,autoRotateSpeed:a.autoRotateSpeed}),p&&b.jsx(x.ContactShadows,{position:p.position,opacity:p.opacity,blur:p.blur})]})})}function q({url:e,position:r=[0,0,0],rotation:s=[0,0,0],scale:n=1,onLoad:i,onError:f}){const{scene:u}=x.useGLTF(e),y=o.useMemo(()=>{const l=u.clone(),t=[],a=new Set,m=[];let p=0;return l.traverse(h=>{if(p++,h.type==="Bone"&&m.push(h.name),h.isMesh){const c=h;t.push(c.name),c.castShadow=!0,c.receiveShadow=!0,(Array.isArray(c.material)?c.material:[c.material]).forEach(g=>a.add(g.name))}}),i&&setTimeout(()=>{i({meshes:t.sort(),materials:Array.from(a).sort(),bones:m.sort(),nodeCount:p})},0),l},[u,i]),d=typeof n=="number"?[n,n,n]:n;return b.jsx("group",{position:r,rotation:s,scale:d,children:b.jsx("primitive",{object:y})})}q.preload=e=>{x.useGLTF.preload(e)};function F(e,r,s){const{actions:n,names:i}=x.useAnimations(e,r),f=o.useRef(null),u=o.useRef(s==null?void 0:s.defaultAnimation);o.useEffect(()=>{if(i.length===0)return;const t=u.current;let a=i[0];if(t){const p=i.find(h=>h===t||h.includes(t));p&&(a=p)}const m=n[a];m&&(m.reset().fadeIn(.5).play(),f.current=m)},[n,i]);const y=o.useCallback((t,a)=>{const{loop:m=!1,crossFadeDuration:p=.2,restoreDefault:h=!0}=a||{};let c=n[t];if(!c){const g=Object.keys(n).find(M=>M.toLowerCase().includes(t.toLowerCase())||t.toLowerCase().includes(M.toLowerCase()));g&&(c=n[g])}if(!c){console.warn(`Animation "${t}" not found. Available: ${i.join(", ")}`);return}const S=f.current;if(!(S===c&&c.isRunning())&&(S&&S!==c&&S.fadeOut(p),c.reset(),c.fadeIn(p),c.setLoop(m?w.LoopRepeat:w.LoopOnce,m?1/0:1),c.clampWhenFinished=!m,c.play(),m||c.getMixer().update(0),f.current=c,h&&!m&&u.current)){const g=c.getMixer(),M=j=>{if(j.action===c){g.removeEventListener("finished",M);const A=n[u.current];A&&(c.fadeOut(p),A.reset().fadeIn(p).play(),f.current=A)}};g.addEventListener("finished",M)}},[n,i]),d=o.useCallback(()=>{var t;(t=f.current)==null||t.fadeOut(.2),f.current=null},[]),l=o.useCallback(()=>i,[i]);return{playAnimation:y,stopAnimation:d,getAnimationNames:l,actions:n}}function O(e,r){const s=o.useRef(r||{}),n=o.useRef([]),i=o.useRef([]);o.useEffect(()=>{const d=new Set,l=[];e.traverse(t=>{t instanceof w.Mesh&&t.morphTargetDictionary&&t.morphTargetInfluences&&(l.push(t),Object.keys(t.morphTargetDictionary).forEach(a=>{d.add(a)}))}),n.current=Array.from(d).sort(),i.current=l},[e]),D.useFrame(()=>{const d=s.current;i.current.forEach(l=>{!l.morphTargetDictionary||!l.morphTargetInfluences||Object.entries(d).forEach(([t,a])=>{const m=l.morphTargetDictionary[t];m!==void 0&&(l.morphTargetInfluences[m]=a)})})}),o.useEffect(()=>{r&&(s.current={...r})},[r]);const f=o.useCallback((d,l)=>{s.current[d]=Math.max(0,Math.min(1,l))},[]),u=o.useCallback(()=>n.current,[]),y=o.useCallback(()=>({...s.current}),[]);return{setMorphTarget:f,getMorphTargetNames:u,getMorphTargetValues:y}}const L=o.createContext(null);function V(){const e=o.useContext(L);if(!e)throw new Error("BoneAttachment must be used within an AnimatedModel");return e}const P=o.forwardRef(({url:e,position:r=[0,0,0],rotation:s=[0,0,0],scale:n=1,defaultAnimation:i,morphTargets:f,onLoad:u,onError:y,children:d},l)=>{const t=o.useRef(null),a=o.useRef([]),{scene:m,animations:p}=x.useGLTF(e),h=o.useMemo(()=>_.clone(m),[m]),{playAnimation:c,stopAnimation:S,getAnimationNames:g}=F(p,h,{defaultAnimation:i}),{setMorphTarget:M}=O(h,f);o.useEffect(()=>{if(!h)return;const C=setTimeout(()=>{const T=[],B=[],N=new Set,k=new Set;let I=0;h.traverse(v=>{if(I++,v.type==="Bone"&&T.push(v.name),v.isMesh){const R=v;B.push(R.name),R.castShadow=!0,R.receiveShadow=!0,(Array.isArray(R.material)?R.material:[R.material]).forEach(E=>{N.add(E.name),E.shadowSide=w.DoubleSide}),R.morphTargetDictionary&&Object.keys(R.morphTargetDictionary).forEach(E=>{k.add(E)})}}),a.current=Array.from(k).sort(),u==null||u({meshes:B.sort(),materials:Array.from(N).sort(),bones:T.sort(),nodeCount:I,animations:p.map(v=>v.name),morphTargetNames:a.current})},0);return()=>clearTimeout(C)},[h,p,u]),o.useImperativeHandle(l,()=>({playAnimation:c,stopAnimation:S,getAnimationNames:g,getGroup:()=>t.current,setMorphTarget:M,getMorphTargetNames:()=>a.current}));const j=o.useMemo(()=>({scene:h,getBone:C=>h.getObjectByName(C)||null}),[h]),A=typeof n=="number"?[n,n,n]:n;return b.jsx(L.Provider,{value:j,children:b.jsxs("group",{ref:t,position:r,rotation:s,scale:A,children:[b.jsx("primitive",{object:h}),d]})})});P.displayName="AnimatedModel";P.preload=e=>{x.useGLTF.preload(e)};const H=o.forwardRef(({url:e,position:r=[0,0,0],rotation:s=[0,0,0],scale:n=1,morphTargets:i,onMorphTargetsFound:f,onLoad:u,onError:y},d)=>{const{scene:l}=x.useGLTF(e),t=o.useMemo(()=>l.clone(),[l]),{setMorphTarget:a,getMorphTargetNames:m,getMorphTargetValues:p}=O(t,i);o.useEffect(()=>{const c=m();c.length>0&&(f==null||f(c))},[t,m,f]),o.useEffect(()=>{if(!t)return;const c=[],S=new Set,g=[];let M=0;t.traverse(j=>{if(M++,j.type==="Bone"&&g.push(j.name),j.isMesh){const A=j;c.push(A.name),A.castShadow=!0,A.receiveShadow=!0,(Array.isArray(A.material)?A.material:[A.material]).forEach(T=>S.add(T.name))}}),u==null||u({meshes:c.sort(),materials:Array.from(S).sort(),bones:g.sort(),nodeCount:M})},[t,u]),o.useImperativeHandle(d,()=>({setMorphTarget:a,getMorphTargetNames:m,getMorphTargetValues:p}));const h=typeof n=="number"?[n,n,n]:n;return b.jsx("group",{position:r,rotation:s,scale:h,children:b.jsx("primitive",{object:t})})});H.displayName="MorphableModel";function J({children:e,bone:r,position:s=[0,0,0],rotation:n=[0,0,0],scale:i=1}){const{getBone:f}=V(),[u,y]=o.useState(null);if(o.useEffect(()=>{const l=f(r);l?y(l):console.warn(`Bone "${r}" not found in model`)},[r,f]),!u)return null;const d=typeof i=="number"?[i,i,i]:i;return D.createPortal(b.jsx("group",{position:s,rotation:n,scale:d,children:e}),u)}function K(e,r){const{scene:s,animations:n}=x.useGLTF(e),i=o.useMemo(()=>_.clone(s),[s]);return o.useEffect(()=>{var l;if(!i)return;const f=[],u=[],y=new Set;let d=0;i.traverse(t=>{if(d++,t.type==="Bone"&&f.push(t.name),t.isMesh){const a=t;u.push(a.name),a.castShadow=!0,a.receiveShadow=!0,(Array.isArray(a.material)?a.material:[a.material]).forEach(p=>{y.add(p.name),p.shadowSide=w.DoubleSide})}}),(l=r==null?void 0:r.onLoad)==null||l.call(r,{meshes:u.sort(),materials:Array.from(y).sort(),bones:f.sort(),nodeCount:d})},[i,r]),{scene:i,animations:n}}function Q(e){x.useGLTF.preload(e)}exports.AnimatedModel=P;exports.BoneAttachment=J;exports.Model=q;exports.MorphableModel=H;exports.Scene3D=z;exports.preloadModel=Q;exports.useAnimationController=F;exports.useClonedModel=K;exports.useMorphTargets=O;
|
|
2
|
+
//# sourceMappingURL=mbt-3d.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mbt-3d.cjs","sources":["../src/lib/components/Scene3D/Scene3D.tsx","../src/lib/components/Model/Model.tsx","../src/lib/hooks/useAnimationController.ts","../src/lib/hooks/useMorphTargets.ts","../src/lib/components/AnimatedModel/AnimatedModelContext.tsx","../src/lib/components/AnimatedModel/AnimatedModel.tsx","../src/lib/components/MorphableModel/MorphableModel.tsx","../src/lib/components/BoneAttachment/BoneAttachment.tsx","../src/lib/hooks/useClonedModel.ts"],"sourcesContent":["import { Suspense, useMemo } from 'react';\r\nimport { Canvas } from '@react-three/fiber';\r\nimport { OrbitControls, ContactShadows, useTexture } from '@react-three/drei';\r\nimport * as THREE from 'three';\r\nimport type { Scene3DProps } from '../../types';\r\n\r\n/**\r\n * Background component that handles both image URLs and solid colors\r\n */\r\nfunction Background({ background }: { background?: string }) {\r\n // Check if it's a color (starts with #) or an image URL\r\n const isColor = background?.startsWith('#');\r\n \r\n if (!background) return null;\r\n \r\n if (isColor) {\r\n return <color attach=\"background\" args={[background]} />;\r\n }\r\n \r\n // It's an image URL\r\n return <BackgroundImage url={background} />;\r\n}\r\n\r\nfunction BackgroundImage({ url }: { url: string }) {\r\n const texture = useTexture(url);\r\n \r\n // Configure texture for background\r\n useMemo(() => {\r\n texture.colorSpace = THREE.SRGBColorSpace;\r\n }, [texture]);\r\n \r\n return <primitive attach=\"background\" object={texture} />;\r\n}\r\n\r\n/**\r\n * Scene3D - Main 3D scene container with built-in canvas, lighting, and controls\r\n * \r\n * @remarks\r\n * This component wraps React Three Fiber's Canvas and provides:\r\n * - OrbitControls for camera rotation (preserves state between model changes)\r\n * - Configurable lighting (ambient + spot light)\r\n * - Optional background (image URL or hex color like \"#1a1a2e\")\r\n * - Optional contact shadows under models\r\n * - Shadow support\r\n * \r\n * @param children - React children to render inside the 3D scene\r\n * @param camera - Camera configuration object. Example: `{ position: [0, 2, 5], fov: 45 }`\r\n * @param camera.position - Camera position as [x, y, z]. Example: `[0, 2, 5]`. Default: `[0, 2, 5]`\r\n * @param camera.fov - Field of view in degrees. Example: `45`. Default: `45`\r\n * @param controls - OrbitControls configuration object. Example: `{ enablePan: false, minDistance: 2, maxDistance: 10 }`\r\n * @param controls.enabled - Enable/disable all controls. Default: `true`\r\n * @param controls.enablePan - Enable camera panning with middle mouse. Default: `true`\r\n * @param controls.enableZoom - Enable camera zoom with scroll wheel. Default: `true`\r\n * @param controls.enableRotate - Enable camera rotation with left mouse. Default: `true`\r\n * @param controls.minDistance - Minimum zoom distance. Example: `2`\r\n * @param controls.maxDistance - Maximum zoom distance. Example: `10`\r\n * @param controls.minPolarAngle - Minimum vertical rotation angle in radians. Example: `Math.PI / 4`\r\n * @param controls.maxPolarAngle - Maximum vertical rotation angle in radians. Example: `Math.PI / 1.8`\r\n * @param controls.autoRotate - Auto-rotate camera around the center. Default: `false`\r\n * @param controls.autoRotateSpeed - Auto-rotation speed. Default: `2`\r\n * @param background - Background: image URL (e.g., `\"/bg.jpg\"`) or hex color (e.g., `\"#1a1a2e\"`)\r\n * @param shadows - Enable shadow rendering. Default: `true`\r\n * @param ambientIntensity - Ambient light intensity (0-1). Example: `0.5`. Default: `0.5`\r\n * @param spotLight - Spot light configuration object. Example: `{ position: [5, 10, 5], intensity: 50 }`\r\n * @param spotLight.position - Light position as [x, y, z]. Example: `[5, 10, 5]`. Default: `[5, 10, 5]`\r\n * @param spotLight.intensity - Light intensity. Example: `50`. Default: `50`\r\n * @param spotLight.castShadow - Enable shadow casting. Default: `true`\r\n * @param contactShadows - Contact shadows configuration. Can be `true` for default settings or object with custom settings. Example: `{ position: [0, -1, 0], opacity: 0.5, blur: 2 }`\r\n * @param contactShadows.position - Shadow position as [x, y, z]. Example: `[0, -1, 0]`. Default: `[0, -1, 0]`\r\n * @param contactShadows.opacity - Shadow opacity (0-1). Example: `0.5`. Default: `0.5`\r\n * @param contactShadows.blur - Shadow blur amount. Example: `2`. Default: `2`\r\n * @param style - Container style object. Example: `{ width: 400, height: 500 }`\r\n * @param className - Container CSS class name\r\n * \r\n * @example\r\n * ```tsx\r\n * <Scene3D\r\n * camera={{ position: [0, 2, 5], fov: 45 }}\r\n * background=\"#1a1a2e\"\r\n * shadows\r\n * style={{ width: 400, height: 500 }}\r\n * >\r\n * <AnimatedModel url=\"/model.glb\" position={[0, -1, 0]} />\r\n * </Scene3D>\r\n * ```\r\n */\r\nexport function Scene3D({\r\n children,\r\n camera = {},\r\n controls = {},\r\n background,\r\n shadows = true,\r\n ambientIntensity = 0.5,\r\n spotLight = {},\r\n contactShadows = true,\r\n style,\r\n className,\r\n}: Scene3DProps) {\r\n const cameraConfig = {\r\n position: camera.position || [0, 2, 5],\r\n fov: camera.fov || 45,\r\n };\r\n\r\n const controlsConfig = {\r\n enabled: controls.enabled ?? true,\r\n enablePan: controls.enablePan ?? true,\r\n enableZoom: controls.enableZoom ?? true,\r\n enableRotate: controls.enableRotate ?? true,\r\n minDistance: controls.minDistance,\r\n maxDistance: controls.maxDistance,\r\n minPolarAngle: controls.minPolarAngle,\r\n maxPolarAngle: controls.maxPolarAngle,\r\n autoRotate: controls.autoRotate ?? false,\r\n autoRotateSpeed: controls.autoRotateSpeed ?? 2,\r\n };\r\n\r\n const spotLightConfig = {\r\n position: spotLight.position || [5, 10, 5],\r\n intensity: spotLight.intensity ?? 50,\r\n castShadow: spotLight.castShadow ?? true,\r\n };\r\n\r\n const contactShadowsConfig =\r\n typeof contactShadows === 'object'\r\n ? {\r\n position: contactShadows.position || [0, -1, 0],\r\n opacity: contactShadows.opacity ?? 0.5,\r\n blur: contactShadows.blur ?? 2,\r\n }\r\n : contactShadows\r\n ? { position: [0, -1, 0] as [number, number, number], opacity: 0.5, blur: 2 }\r\n : null;\r\n\r\n return (\r\n <div style={style} className={className}>\r\n <Canvas\r\n shadows={shadows}\r\n camera={{\r\n position: cameraConfig.position as [number, number, number],\r\n fov: cameraConfig.fov,\r\n }}\r\n style={{ width: '100%', height: '100%' }}\r\n >\r\n {/* Background */}\r\n <Suspense fallback={null}>\r\n <Background background={background} />\r\n </Suspense>\r\n\r\n {/* Lighting */}\r\n <ambientLight intensity={ambientIntensity} />\r\n <spotLight\r\n position={spotLightConfig.position}\r\n intensity={spotLightConfig.intensity}\r\n castShadow={spotLightConfig.castShadow}\r\n />\r\n\r\n {/* Content */}\r\n <Suspense fallback={null}>{children}</Suspense>\r\n\r\n {/* Controls - keeps camera state between model changes */}\r\n <OrbitControls\r\n makeDefault\r\n enabled={controlsConfig.enabled}\r\n enablePan={controlsConfig.enablePan}\r\n enableZoom={controlsConfig.enableZoom}\r\n enableRotate={controlsConfig.enableRotate}\r\n minDistance={controlsConfig.minDistance}\r\n maxDistance={controlsConfig.maxDistance}\r\n minPolarAngle={controlsConfig.minPolarAngle}\r\n maxPolarAngle={controlsConfig.maxPolarAngle}\r\n autoRotate={controlsConfig.autoRotate}\r\n autoRotateSpeed={controlsConfig.autoRotateSpeed}\r\n />\r\n\r\n {/* Contact Shadows */}\r\n {contactShadowsConfig && (\r\n <ContactShadows\r\n position={contactShadowsConfig.position}\r\n opacity={contactShadowsConfig.opacity}\r\n blur={contactShadowsConfig.blur}\r\n />\r\n )}\r\n </Canvas>\r\n </div>\r\n );\r\n}\r\n","import { useMemo } from 'react';\r\nimport { useGLTF } from '@react-three/drei';\r\nimport * as THREE from 'three';\r\nimport type { ModelProps } from '../../types';\r\n\r\n/**\r\n * Model - Simple 3D model component for loading and displaying GLB/GLTF files\r\n * \r\n * @remarks\r\n * This component:\r\n * - Loads GLB/GLTF models using drei's useGLTF hook\r\n * - Automatically clones the scene for independent instances\r\n * - Configures shadows on all meshes\r\n * - Supports position, rotation, and scale transforms\r\n * - Provides onLoad callback with model metadata\r\n * \r\n * @param url - URL or path to GLB/GLTF model file. Example: `\"/models/sword.glb\"`\r\n * @param position - Model position in 3D space as [x, y, z]. Example: `[0, 0, 0]`. Default: `[0, 0, 0]`\r\n * @param rotation - Model rotation in radians as [x, y, z]. Example: `[0, Math.PI/2, 0]`. Default: `[0, 0, 0]`\r\n * @param scale - Uniform scale (number) or per-axis scale [x, y, z]. Examples: `0.5` or `[1, 2, 1]`. Default: `1`\r\n * @param onLoad - Callback when model finishes loading. Receives ModelInfo object with metadata: `{ meshes, materials, bones, nodeCount }`\r\n * @param onError - Callback when model fails to load. Receives Error object\r\n * \r\n * @example\r\n * ```tsx\r\n * <Model \r\n * url=\"/models/sword.glb\"\r\n * position={[0, 0, 0]}\r\n * rotation={[0, Math.PI/2, 0]}\r\n * scale={0.5}\r\n * onLoad={(info) => console.log('Loaded:', info)}\r\n * />\r\n * ```\r\n */\r\nexport function Model({\r\n url,\r\n position = [0, 0, 0],\r\n rotation = [0, 0, 0],\r\n scale = 1,\r\n onLoad,\r\n onError: _onError,\r\n}: ModelProps) {\r\n const { scene } = useGLTF(url);\r\n\r\n // Clone scene for independent instance\r\n const clonedScene = useMemo(() => {\r\n const clone = scene.clone();\r\n \r\n // Setup shadows and collect info\r\n const meshes: string[] = [];\r\n const materials = new Set<string>();\r\n const bones: string[] = [];\r\n let nodeCount = 0;\r\n\r\n clone.traverse((child) => {\r\n nodeCount++;\r\n \r\n if (child.type === 'Bone') {\r\n bones.push(child.name);\r\n }\r\n \r\n if ((child as THREE.Mesh).isMesh) {\r\n const mesh = child as THREE.Mesh;\r\n meshes.push(mesh.name);\r\n mesh.castShadow = true;\r\n mesh.receiveShadow = true;\r\n \r\n const mats = Array.isArray(mesh.material) ? mesh.material : [mesh.material];\r\n mats.forEach((mat) => materials.add(mat.name));\r\n }\r\n });\r\n\r\n // Defer callback to avoid state update during render\r\n if (onLoad) {\r\n setTimeout(() => {\r\n onLoad({\r\n meshes: meshes.sort(),\r\n materials: Array.from(materials).sort(),\r\n bones: bones.sort(),\r\n nodeCount,\r\n });\r\n }, 0);\r\n }\r\n\r\n return clone;\r\n }, [scene, onLoad]);\r\n\r\n const scaleArray = typeof scale === 'number' ? [scale, scale, scale] : scale;\r\n\r\n return (\r\n <group position={position} rotation={rotation} scale={scaleArray as [number, number, number]}>\r\n <primitive object={clonedScene} />\r\n </group>\r\n );\r\n}\r\n\r\n// Preload helper\r\nModel.preload = (url: string) => {\r\n useGLTF.preload(url);\r\n};\r\n","import { useRef, useCallback, useEffect } from 'react';\r\nimport { useAnimations } from '@react-three/drei';\r\nimport * as THREE from 'three';\r\n\r\ninterface UseAnimationControllerOptions {\r\n defaultAnimation?: string;\r\n}\r\n\r\nexport function useAnimationController(\r\n animations: THREE.AnimationClip[],\r\n scene: THREE.Object3D,\r\n options?: UseAnimationControllerOptions\r\n) {\r\n const { actions, names } = useAnimations(animations, scene);\r\n const currentActionRef = useRef<THREE.AnimationAction | null>(null);\r\n const defaultAnimationRef = useRef<string | undefined>(options?.defaultAnimation);\r\n\r\n // Play default animation on mount\r\n useEffect(() => {\r\n if (names.length === 0) return;\r\n\r\n const defaultAnim = defaultAnimationRef.current;\r\n let animToPlay = names[0];\r\n\r\n if (defaultAnim) {\r\n const match = names.find((n) => n === defaultAnim || n.includes(defaultAnim));\r\n if (match) animToPlay = match;\r\n }\r\n\r\n const action = actions[animToPlay];\r\n if (action) {\r\n action.reset().fadeIn(0.5).play();\r\n currentActionRef.current = action;\r\n }\r\n }, [actions, names]);\r\n\r\n const playAnimation = useCallback(\r\n (\r\n name: string,\r\n opts?: {\r\n loop?: boolean;\r\n crossFadeDuration?: number;\r\n restoreDefault?: boolean;\r\n }\r\n ) => {\r\n const {\r\n loop = false,\r\n crossFadeDuration = 0.2,\r\n restoreDefault = true,\r\n } = opts || {};\r\n\r\n // Find animation (exact match or partial)\r\n let targetAction = actions[name];\r\n\r\n if (!targetAction) {\r\n const match = Object.keys(actions).find(\r\n (a) =>\r\n a.toLowerCase().includes(name.toLowerCase()) ||\r\n name.toLowerCase().includes(a.toLowerCase())\r\n );\r\n if (match) {\r\n targetAction = actions[match];\r\n }\r\n }\r\n\r\n if (!targetAction) {\r\n console.warn(`Animation \"${name}\" not found. Available: ${names.join(', ')}`);\r\n return;\r\n }\r\n\r\n // Don't restart if already playing\r\n const prev = currentActionRef.current;\r\n if (prev === targetAction && targetAction.isRunning()) {\r\n return;\r\n }\r\n\r\n // Crossfade from previous\r\n if (prev && prev !== targetAction) {\r\n prev.fadeOut(crossFadeDuration);\r\n }\r\n\r\n // Setup new action\r\n targetAction.reset();\r\n targetAction.fadeIn(crossFadeDuration);\r\n targetAction.setLoop(\r\n loop ? THREE.LoopRepeat : THREE.LoopOnce,\r\n loop ? Infinity : 1\r\n );\r\n targetAction.clampWhenFinished = !loop;\r\n targetAction.play();\r\n\r\n // Force immediate update for non-looping animations\r\n if (!loop) {\r\n targetAction.getMixer().update(0);\r\n }\r\n\r\n currentActionRef.current = targetAction;\r\n\r\n // Restore default animation after one-shot completes\r\n if (restoreDefault && !loop && defaultAnimationRef.current) {\r\n const mixer = targetAction.getMixer();\r\n const onFinished = (e: { action: THREE.AnimationAction }) => {\r\n if (e.action === targetAction) {\r\n mixer.removeEventListener('finished', onFinished);\r\n \r\n const defaultAction = actions[defaultAnimationRef.current!];\r\n if (defaultAction) {\r\n targetAction.fadeOut(crossFadeDuration);\r\n defaultAction.reset().fadeIn(crossFadeDuration).play();\r\n currentActionRef.current = defaultAction;\r\n }\r\n }\r\n };\r\n mixer.addEventListener('finished', onFinished);\r\n }\r\n },\r\n [actions, names]\r\n );\r\n\r\n const stopAnimation = useCallback(() => {\r\n currentActionRef.current?.fadeOut(0.2);\r\n currentActionRef.current = null;\r\n }, []);\r\n\r\n const getAnimationNames = useCallback(() => names, [names]);\r\n\r\n return {\r\n playAnimation,\r\n stopAnimation,\r\n getAnimationNames,\r\n actions,\r\n };\r\n}\r\n","import { useRef, useCallback, useEffect } from 'react';\r\nimport { useFrame } from '@react-three/fiber';\r\nimport * as THREE from 'three';\r\n\r\nexport function useMorphTargets(\r\n scene: THREE.Object3D,\r\n initialValues?: Record<string, number>\r\n) {\r\n const morphValuesRef = useRef<Record<string, number>>(initialValues || {});\r\n const morphNamesRef = useRef<string[]>([]);\r\n const meshesWithMorphsRef = useRef<THREE.Mesh[]>([]);\r\n\r\n // Discover morph targets\r\n useEffect(() => {\r\n const names = new Set<string>();\r\n const meshes: THREE.Mesh[] = [];\r\n\r\n scene.traverse((child) => {\r\n if (\r\n child instanceof THREE.Mesh &&\r\n child.morphTargetDictionary &&\r\n child.morphTargetInfluences\r\n ) {\r\n meshes.push(child);\r\n Object.keys(child.morphTargetDictionary).forEach((name) => {\r\n names.add(name);\r\n });\r\n }\r\n });\r\n\r\n morphNamesRef.current = Array.from(names).sort();\r\n meshesWithMorphsRef.current = meshes;\r\n }, [scene]);\r\n\r\n // Apply morph targets every frame\r\n useFrame(() => {\r\n const values = morphValuesRef.current;\r\n \r\n meshesWithMorphsRef.current.forEach((mesh) => {\r\n if (!mesh.morphTargetDictionary || !mesh.morphTargetInfluences) return;\r\n \r\n Object.entries(values).forEach(([name, value]) => {\r\n const index = mesh.morphTargetDictionary![name];\r\n if (index !== undefined) {\r\n mesh.morphTargetInfluences![index] = value;\r\n }\r\n });\r\n });\r\n });\r\n\r\n // Update values when props change\r\n useEffect(() => {\r\n if (initialValues) {\r\n morphValuesRef.current = { ...initialValues };\r\n }\r\n }, [initialValues]);\r\n\r\n const setMorphTarget = useCallback((name: string, value: number) => {\r\n morphValuesRef.current[name] = Math.max(0, Math.min(1, value));\r\n }, []);\r\n\r\n const getMorphTargetNames = useCallback(() => morphNamesRef.current, []);\r\n\r\n const getMorphTargetValues = useCallback(() => ({ ...morphValuesRef.current }), []);\r\n\r\n return {\r\n setMorphTarget,\r\n getMorphTargetNames,\r\n getMorphTargetValues,\r\n };\r\n}\r\n","import { createContext, useContext } from 'react';\r\nimport type { AnimatedModelContextValue } from '../../types';\r\n\r\nexport const AnimatedModelContext = createContext<AnimatedModelContextValue | null>(null);\r\n\r\nexport function useAnimatedModelContext() {\r\n const context = useContext(AnimatedModelContext);\r\n if (!context) {\r\n throw new Error('BoneAttachment must be used within an AnimatedModel');\r\n }\r\n return context;\r\n}\r\n","import {\r\n forwardRef,\r\n useImperativeHandle,\r\n useRef,\r\n useMemo,\r\n useEffect,\r\n} from 'react';\r\nimport * as THREE from 'three';\r\nimport { useGLTF } from '@react-three/drei';\r\nimport * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils.js';\r\nimport { useAnimationController } from '../../hooks/useAnimationController';\r\nimport { useMorphTargets } from '../../hooks/useMorphTargets';\r\nimport { AnimatedModelContext } from './AnimatedModelContext';\r\nimport type { AnimatedModelProps, AnimatedModelHandle } from '../../types';\r\n\r\n/**\r\n * AnimatedModel - 3D model with animation support and bone attachment capability\r\n * \r\n * @remarks\r\n * This component:\r\n * - Loads GLB/GLTF models with animations using useGLTF\r\n * - Properly clones skinned meshes using SkeletonUtils for multiple instances\r\n * - Plays animations with automatic crossfading\r\n * - Supports morph targets for character customization\r\n * - Provides context for BoneAttachment children\r\n * - Exposes imperative API via ref for external animation control\r\n * \r\n * The ref provides these methods:\r\n * - `playAnimation(name, options)` - Play an animation with optional loop/restore\r\n * - `stopAnimation()` - Stop current animation\r\n * - `getAnimationNames()` - Get list of available animations\r\n * - `getGroup()` - Get the THREE.Group for advanced manipulation\r\n * - `setMorphTarget(name, value)` - Set morph target value\r\n * - `getMorphTargetNames()` - Get available morph target names\r\n * \r\n * @param ref - React ref for imperative API access\r\n * @param url - URL or path to GLB/GLTF model file with animations. Example: `\"/models/character.glb\"`\r\n * @param position - Model position in 3D space as [x, y, z]. Example: `[0, -1, 0]`. Default: `[0, 0, 0]`\r\n * @param rotation - Model rotation in radians as [x, y, z]. Example: `[0, Math.PI, 0]`. Default: `[0, 0, 0]`\r\n * @param scale - Uniform scale (number) or per-axis scale [x, y, z]. Examples: `0.5` or `[1, 2, 1]`. Default: `1`\r\n * @param defaultAnimation - Name of animation to play on load. If not specified, plays first animation. Example: `\"Idle\"`\r\n * @param morphTargets - Morph target values as key-value pairs where value is 0-1. Example: `{ muscular: 0.5, thin: 0.2 }`\r\n * @param children - Child components, typically BoneAttachment components for attaching items\r\n * @param onLoad - Callback when model loads. Receives AnimatedModelInfo object: `{ meshes, materials, bones, nodeCount, animations, morphTargetNames }`\r\n * @param onError - Callback when model fails to load. Receives Error object\r\n * \r\n * @example\r\n * ```tsx\r\n * const modelRef = useRef<AnimatedModelHandle>(null);\r\n * \r\n * <AnimatedModel\r\n * ref={modelRef}\r\n * url=\"/models/character.glb\"\r\n * defaultAnimation=\"Idle\"\r\n * morphTargets={{ muscular: 0.5 }}\r\n * position={[0, -1, 0]}\r\n * >\r\n * <BoneAttachment bone=\"hand_r\">\r\n * <Model url=\"/models/sword.glb\" />\r\n * </BoneAttachment>\r\n * </AnimatedModel>\r\n * \r\n * // Control animations from outside\r\n * modelRef.current?.playAnimation('Attack', { loop: false });\r\n * ```\r\n */\r\nexport const AnimatedModel = forwardRef<AnimatedModelHandle, AnimatedModelProps>(\r\n (\r\n {\r\n url,\r\n position = [0, 0, 0],\r\n rotation = [0, 0, 0],\r\n scale = 1,\r\n defaultAnimation,\r\n morphTargets,\r\n onLoad,\r\n onError: _onError,\r\n children,\r\n },\r\n ref\r\n ) => {\r\n const groupRef = useRef<THREE.Group>(null);\r\n const morphNamesRef = useRef<string[]>([]);\r\n\r\n // Load and clone model\r\n const { scene: gltfScene, animations } = useGLTF(url);\r\n const scene = useMemo(() => SkeletonUtils.clone(gltfScene), [gltfScene]);\r\n\r\n // Animation controller\r\n const { playAnimation, stopAnimation, getAnimationNames } = useAnimationController(\r\n animations,\r\n scene,\r\n { defaultAnimation }\r\n );\r\n\r\n // Morph targets\r\n const { setMorphTarget } = useMorphTargets(\r\n scene,\r\n morphTargets\r\n );\r\n\r\n // Discover morph targets and call onLoad\r\n useEffect(() => {\r\n if (!scene) return;\r\n\r\n const timer = setTimeout(() => {\r\n const bones: string[] = [];\r\n const meshes: string[] = [];\r\n const materials = new Set<string>();\r\n const morphNames = new Set<string>();\r\n let nodeCount = 0;\r\n\r\n scene.traverse((child) => {\r\n nodeCount++;\r\n \r\n if (child.type === 'Bone') {\r\n bones.push(child.name);\r\n }\r\n \r\n if ((child as THREE.Mesh).isMesh) {\r\n const mesh = child as THREE.Mesh;\r\n meshes.push(mesh.name);\r\n \r\n // Setup shadows\r\n mesh.castShadow = true;\r\n mesh.receiveShadow = true;\r\n \r\n const mats = Array.isArray(mesh.material) ? mesh.material : [mesh.material];\r\n mats.forEach((mat) => {\r\n materials.add(mat.name);\r\n mat.shadowSide = THREE.DoubleSide;\r\n });\r\n\r\n if (mesh.morphTargetDictionary) {\r\n Object.keys(mesh.morphTargetDictionary).forEach((name) => {\r\n morphNames.add(name);\r\n });\r\n }\r\n }\r\n });\r\n\r\n morphNamesRef.current = Array.from(morphNames).sort();\r\n\r\n onLoad?.({\r\n meshes: meshes.sort(),\r\n materials: Array.from(materials).sort(),\r\n bones: bones.sort(),\r\n nodeCount,\r\n animations: animations.map((a) => a.name),\r\n morphTargetNames: morphNamesRef.current,\r\n });\r\n }, 0);\r\n\r\n return () => clearTimeout(timer);\r\n }, [scene, animations, onLoad]);\r\n\r\n // Expose imperative handle\r\n useImperativeHandle(ref, () => ({\r\n playAnimation,\r\n stopAnimation,\r\n getAnimationNames,\r\n getGroup: () => groupRef.current,\r\n setMorphTarget,\r\n getMorphTargetNames: () => morphNamesRef.current,\r\n }));\r\n\r\n // Context for BoneAttachment children\r\n const contextValue = useMemo(\r\n () => ({\r\n scene,\r\n getBone: (name: string) => scene.getObjectByName(name) || null,\r\n }),\r\n [scene]\r\n );\r\n\r\n const scaleArray = typeof scale === 'number' ? [scale, scale, scale] : scale;\r\n\r\n return (\r\n <AnimatedModelContext.Provider value={contextValue}>\r\n <group\r\n ref={groupRef}\r\n position={position}\r\n rotation={rotation}\r\n scale={scaleArray as [number, number, number]}\r\n >\r\n <primitive object={scene} />\r\n {children}\r\n </group>\r\n </AnimatedModelContext.Provider>\r\n );\r\n }\r\n);\r\n\r\nAnimatedModel.displayName = 'AnimatedModel';\r\n\r\n// Preload helper\r\n(AnimatedModel as any).preload = (url: string) => {\r\n useGLTF.preload(url);\r\n};\r\n","import { forwardRef, useImperativeHandle, useMemo, useEffect } from 'react';\r\nimport { useGLTF } from '@react-three/drei';\r\nimport * as THREE from 'three';\r\nimport { useMorphTargets } from '../../hooks/useMorphTargets';\r\nimport type { MorphableModelProps, MorphableModelHandle } from '../../types';\r\n\r\n/**\r\n * MorphableModel - 3D model with morph targets for shape customization\r\n * \r\n * @remarks\r\n * This component:\r\n * - Loads GLB/GLTF models with morph targets (blend shapes)\r\n * - Dynamically discovers available morph targets from the model\r\n * - Applies morph target values in real-time\r\n * - Provides imperative API via ref for programmatic control\r\n * \r\n * Morph targets are commonly used for:\r\n * - Character customization (muscular, thin, fat)\r\n * - Facial expressions (smile, frown, blink)\r\n * - Clothing fit adjustments\r\n * \r\n * @param ref - React ref for imperative API access\r\n * @param url - URL or path to GLB/GLTF model file with morph targets. Example: `\"/models/character.glb\"`\r\n * @param position - Model position in 3D space as [x, y, z]. Example: `[0, -1, 0]`. Default: `[0, 0, 0]`\r\n * @param rotation - Model rotation in radians as [x, y, z]. Example: `[0, Math.PI, 0]`. Default: `[0, 0, 0]`\r\n * @param scale - Uniform scale (number) or per-axis scale [x, y, z]. Examples: `0.5` or `[1, 2, 1]`. Default: `1`\r\n * @param morphTargets - Morph target values as key-value pairs where value is 0-1. Example: `{ muscular: 0.5, thin: 0.2 }`\r\n * @param onMorphTargetsFound - Callback when morph targets are discovered from the model. Receives array of morph target names: `[\"muscular\", \"thin\", \"fat\"]`\r\n * @param onLoad - Callback when model finishes loading. Receives ModelInfo object with metadata: `{ meshes, materials, bones, nodeCount }`\r\n * @param onError - Callback when model fails to load. Receives Error object\r\n * \r\n * @example\r\n * ```tsx\r\n * const modelRef = useRef<MorphableModelHandle>(null);\r\n * const [morphs, setMorphs] = useState({ muscular: 0.5, thin: 0.2 });\r\n * \r\n * <MorphableModel\r\n * ref={modelRef}\r\n * url=\"/models/character.glb\"\r\n * morphTargets={morphs}\r\n * onMorphTargetsFound={(names) => console.log('Available:', names)}\r\n * position={[0, -1, 0]}\r\n * />\r\n * \r\n * // Control programmatically\r\n * modelRef.current?.setMorphTarget('muscular', 0.8);\r\n * ```\r\n */\r\nexport const MorphableModel = forwardRef<MorphableModelHandle, MorphableModelProps>(\r\n (\r\n {\r\n url,\r\n position = [0, 0, 0],\r\n rotation = [0, 0, 0],\r\n scale = 1,\r\n morphTargets,\r\n onMorphTargetsFound,\r\n onLoad,\r\n onError: _onError,\r\n },\r\n ref\r\n ) => {\r\n const { scene } = useGLTF(url);\r\n\r\n // Clone scene\r\n const clonedScene = useMemo(() => scene.clone(), [scene]);\r\n\r\n // Morph targets hook\r\n const { setMorphTarget, getMorphTargetNames, getMorphTargetValues } = useMorphTargets(\r\n clonedScene,\r\n morphTargets\r\n );\r\n\r\n // Discover morph targets and notify\r\n useEffect(() => {\r\n const names = getMorphTargetNames();\r\n if (names.length > 0) {\r\n onMorphTargetsFound?.(names);\r\n }\r\n }, [clonedScene, getMorphTargetNames, onMorphTargetsFound]);\r\n\r\n // Setup shadows and call onLoad\r\n useEffect(() => {\r\n if (!clonedScene) return;\r\n\r\n const meshes: string[] = [];\r\n const materials = new Set<string>();\r\n const bones: string[] = [];\r\n let nodeCount = 0;\r\n\r\n clonedScene.traverse((child) => {\r\n nodeCount++;\r\n \r\n if (child.type === 'Bone') {\r\n bones.push(child.name);\r\n }\r\n \r\n if ((child as THREE.Mesh).isMesh) {\r\n const mesh = child as THREE.Mesh;\r\n meshes.push(mesh.name);\r\n mesh.castShadow = true;\r\n mesh.receiveShadow = true;\r\n \r\n const mats = Array.isArray(mesh.material) ? mesh.material : [mesh.material];\r\n mats.forEach((mat) => materials.add(mat.name));\r\n }\r\n });\r\n\r\n onLoad?.({\r\n meshes: meshes.sort(),\r\n materials: Array.from(materials).sort(),\r\n bones: bones.sort(),\r\n nodeCount,\r\n });\r\n }, [clonedScene, onLoad]);\r\n\r\n // Expose handle\r\n useImperativeHandle(ref, () => ({\r\n setMorphTarget,\r\n getMorphTargetNames,\r\n getMorphTargetValues,\r\n }));\r\n\r\n const scaleArray = typeof scale === 'number' ? [scale, scale, scale] : scale;\r\n\r\n return (\r\n <group position={position} rotation={rotation} scale={scaleArray as [number, number, number]}>\r\n <primitive object={clonedScene} />\r\n </group>\r\n );\r\n }\r\n);\r\n\r\nMorphableModel.displayName = 'MorphableModel';\r\n","import { useState, useEffect } from 'react';\r\nimport { createPortal } from '@react-three/fiber';\r\nimport * as THREE from 'three';\r\nimport { useAnimatedModelContext } from '../AnimatedModel/AnimatedModelContext';\r\nimport type { BoneAttachmentProps } from '../../types';\r\n\r\n/**\r\n * BoneAttachment - Attach models to bones of an AnimatedModel parent\r\n * \r\n * @remarks\r\n * This component:\r\n * - Must be used as a child of AnimatedModel\r\n * - Uses React Three Fiber's createPortal to attach to a bone\r\n * - Automatically follows bone transformations during animations\r\n * - Supports position/rotation/scale offsets relative to the bone\r\n * \r\n * Common bone names:\r\n * - Hand bones: \"hand_r\", \"hand_l\", \"DEF-handR\", \"DEF-handL\"\r\n * - Spine/Back: \"spine\", \"spine_upper\", \"back\"\r\n * - Head: \"head\", \"neck\"\r\n * \r\n * @param children - Child components to attach to the bone (typically Model components)\r\n * @param bone - Bone name to attach to. Must match bone name in the parent AnimatedModel. Example: `\"hand_r\"` or `\"DEF-handR\"`\r\n * @param position - Position offset relative to the bone as [x, y, z]. Example: `[0.1, 0, 0]`. Default: `[0, 0, 0]`\r\n * @param rotation - Rotation offset relative to the bone in radians as [x, y, z]. Example: `[0, Math.PI/2, 0]`. Default: `[0, 0, 0]`\r\n * @param scale - Uniform scale (number) or per-axis scale [x, y, z]. Examples: `0.7` or `[1, 2, 1]`. Default: `1`\r\n * \r\n * @example\r\n * ```tsx\r\n * <AnimatedModel url=\"/character.glb\">\r\n * // Sword in right hand\r\n * <BoneAttachment \r\n * bone=\"hand_r\"\r\n * position={[0.1, 0, 0]}\r\n * rotation={[0, Math.PI/2, 0]}\r\n * >\r\n * <Model url=\"/sword.glb\" scale={0.7} />\r\n * </BoneAttachment>\r\n * \r\n * // Shield on back\r\n * <BoneAttachment bone=\"spine_upper\">\r\n * <Model url=\"/shield.glb\" />\r\n * </BoneAttachment>\r\n * </AnimatedModel>\r\n * ```\r\n */\r\nexport function BoneAttachment({\r\n children,\r\n bone,\r\n position = [0, 0, 0],\r\n rotation = [0, 0, 0],\r\n scale = 1,\r\n}: BoneAttachmentProps) {\r\n const { getBone } = useAnimatedModelContext();\r\n const [boneObject, setBoneObject] = useState<THREE.Object3D | null>(null);\r\n\r\n useEffect(() => {\r\n const found = getBone(bone);\r\n if (found) {\r\n setBoneObject(found);\r\n } else {\r\n console.warn(`Bone \"${bone}\" not found in model`);\r\n }\r\n }, [bone, getBone]);\r\n\r\n if (!boneObject) {\r\n return null;\r\n }\r\n\r\n const scaleArray = typeof scale === 'number' ? [scale, scale, scale] : scale;\r\n\r\n return createPortal(\r\n <group\r\n position={position}\r\n rotation={rotation}\r\n scale={scaleArray as [number, number, number]}\r\n >\r\n {children}\r\n </group>,\r\n boneObject\r\n );\r\n}\r\n","import { useMemo, useEffect } from 'react';\r\nimport { useGLTF } from '@react-three/drei';\r\nimport * as THREE from 'three';\r\nimport * as SkeletonUtils from 'three/examples/jsm/utils/SkeletonUtils.js';\r\nimport type { ModelInfo } from '../types';\r\n\r\ninterface UseClonedModelOptions {\r\n onLoad?: (info: ModelInfo) => void;\r\n onError?: (error: Error) => void;\r\n}\r\n\r\nexport function useClonedModel(url: string, options?: UseClonedModelOptions) {\r\n const { scene, animations } = useGLTF(url);\r\n \r\n // Clone scene using SkeletonUtils for proper skinned mesh handling\r\n const clonedScene = useMemo(() => {\r\n return SkeletonUtils.clone(scene);\r\n }, [scene]);\r\n\r\n // Extract model info and setup shadows\r\n useEffect(() => {\r\n if (!clonedScene) return;\r\n\r\n const bones: string[] = [];\r\n const meshes: string[] = [];\r\n const materials = new Set<string>();\r\n let nodeCount = 0;\r\n\r\n clonedScene.traverse((child) => {\r\n nodeCount++;\r\n \r\n if (child.type === 'Bone') {\r\n bones.push(child.name);\r\n }\r\n \r\n if ((child as THREE.Mesh).isMesh) {\r\n const mesh = child as THREE.Mesh;\r\n meshes.push(mesh.name);\r\n \r\n // Setup shadows\r\n mesh.castShadow = true;\r\n mesh.receiveShadow = true;\r\n \r\n // Collect material names\r\n const mats = Array.isArray(mesh.material) ? mesh.material : [mesh.material];\r\n mats.forEach((mat) => {\r\n materials.add(mat.name);\r\n mat.shadowSide = THREE.DoubleSide;\r\n });\r\n }\r\n });\r\n\r\n options?.onLoad?.({\r\n meshes: meshes.sort(),\r\n materials: Array.from(materials).sort(),\r\n bones: bones.sort(),\r\n nodeCount,\r\n });\r\n }, [clonedScene, options]);\r\n\r\n return { scene: clonedScene, animations };\r\n}\r\n\r\n// Preload helper\r\nexport function preloadModel(url: string) {\r\n useGLTF.preload(url);\r\n}\r\n"],"names":["Background","background","isColor","jsx","BackgroundImage","url","texture","useTexture","useMemo","THREE","Scene3D","children","camera","controls","shadows","ambientIntensity","spotLight","contactShadows","style","className","cameraConfig","controlsConfig","spotLightConfig","contactShadowsConfig","jsxs","Canvas","Suspense","OrbitControls","ContactShadows","Model","position","rotation","scale","onLoad","_onError","scene","useGLTF","clonedScene","clone","meshes","materials","bones","nodeCount","child","mesh","mat","scaleArray","useAnimationController","animations","options","actions","names","useAnimations","currentActionRef","useRef","defaultAnimationRef","useEffect","defaultAnim","animToPlay","match","n","action","playAnimation","useCallback","name","opts","loop","crossFadeDuration","restoreDefault","targetAction","a","prev","mixer","onFinished","e","defaultAction","stopAnimation","_a","getAnimationNames","useMorphTargets","initialValues","morphValuesRef","morphNamesRef","meshesWithMorphsRef","useFrame","values","value","index","setMorphTarget","getMorphTargetNames","getMorphTargetValues","AnimatedModelContext","createContext","useAnimatedModelContext","context","useContext","AnimatedModel","forwardRef","defaultAnimation","morphTargets","ref","groupRef","gltfScene","SkeletonUtils","timer","morphNames","useImperativeHandle","contextValue","MorphableModel","onMorphTargetsFound","BoneAttachment","bone","getBone","boneObject","setBoneObject","useState","found","createPortal","useClonedModel","preloadModel"],"mappings":"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"}
|
package/dist/mbt-3d.js
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { Suspense as Z, useMemo as D, useRef as R, useEffect as M, useCallback as T, createContext as q, useContext as z, forwardRef as G, useImperativeHandle as W, useState as F } from "react";
|
|
2
|
+
import { OrbitControls as J, ContactShadows as K, useTexture as L, useGLTF as x, useAnimations as Q } from "@react-three/drei";
|
|
3
|
+
import * as E from "three";
|
|
4
|
+
import * as _ from "three/examples/jsm/utils/SkeletonUtils.js";
|
|
5
|
+
import { Canvas as X, useFrame as Y, createPortal as ee } from "@react-three/fiber";
|
|
6
|
+
import { jsx as y, jsxs as $ } from "react/jsx-runtime";
|
|
7
|
+
function te({ background: r }) {
|
|
8
|
+
const o = r == null ? void 0 : r.startsWith("#");
|
|
9
|
+
return r ? o ? /* @__PURE__ */ y("color", { attach: "background", args: [r] }) : /* @__PURE__ */ y(ne, { url: r }) : null;
|
|
10
|
+
}
|
|
11
|
+
function ne({ url: r }) {
|
|
12
|
+
const o = L(r);
|
|
13
|
+
return D(() => {
|
|
14
|
+
o.colorSpace = E.SRGBColorSpace;
|
|
15
|
+
}, [o]), /* @__PURE__ */ y("primitive", { attach: "background", object: o });
|
|
16
|
+
}
|
|
17
|
+
function fe({
|
|
18
|
+
children: r,
|
|
19
|
+
camera: o = {},
|
|
20
|
+
controls: i = {},
|
|
21
|
+
background: t,
|
|
22
|
+
shadows: a = !0,
|
|
23
|
+
ambientIntensity: l = 0.5,
|
|
24
|
+
spotLight: c = {},
|
|
25
|
+
contactShadows: d = !0,
|
|
26
|
+
style: h,
|
|
27
|
+
className: m
|
|
28
|
+
}) {
|
|
29
|
+
const e = {
|
|
30
|
+
position: o.position || [0, 2, 5],
|
|
31
|
+
fov: o.fov || 45
|
|
32
|
+
}, n = {
|
|
33
|
+
enabled: i.enabled ?? !0,
|
|
34
|
+
enablePan: i.enablePan ?? !0,
|
|
35
|
+
enableZoom: i.enableZoom ?? !0,
|
|
36
|
+
enableRotate: i.enableRotate ?? !0,
|
|
37
|
+
minDistance: i.minDistance,
|
|
38
|
+
maxDistance: i.maxDistance,
|
|
39
|
+
minPolarAngle: i.minPolarAngle,
|
|
40
|
+
maxPolarAngle: i.maxPolarAngle,
|
|
41
|
+
autoRotate: i.autoRotate ?? !1,
|
|
42
|
+
autoRotateSpeed: i.autoRotateSpeed ?? 2
|
|
43
|
+
}, u = {
|
|
44
|
+
position: c.position || [5, 10, 5],
|
|
45
|
+
intensity: c.intensity ?? 50,
|
|
46
|
+
castShadow: c.castShadow ?? !0
|
|
47
|
+
}, f = typeof d == "object" ? {
|
|
48
|
+
position: d.position || [0, -1, 0],
|
|
49
|
+
opacity: d.opacity ?? 0.5,
|
|
50
|
+
blur: d.blur ?? 2
|
|
51
|
+
} : d ? { position: [0, -1, 0], opacity: 0.5, blur: 2 } : null;
|
|
52
|
+
return /* @__PURE__ */ y("div", { style: h, className: m, children: /* @__PURE__ */ $(
|
|
53
|
+
X,
|
|
54
|
+
{
|
|
55
|
+
shadows: a,
|
|
56
|
+
camera: {
|
|
57
|
+
position: e.position,
|
|
58
|
+
fov: e.fov
|
|
59
|
+
},
|
|
60
|
+
style: { width: "100%", height: "100%" },
|
|
61
|
+
children: [
|
|
62
|
+
/* @__PURE__ */ y(Z, { fallback: null, children: /* @__PURE__ */ y(te, { background: t }) }),
|
|
63
|
+
/* @__PURE__ */ y("ambientLight", { intensity: l }),
|
|
64
|
+
/* @__PURE__ */ y(
|
|
65
|
+
"spotLight",
|
|
66
|
+
{
|
|
67
|
+
position: u.position,
|
|
68
|
+
intensity: u.intensity,
|
|
69
|
+
castShadow: u.castShadow
|
|
70
|
+
}
|
|
71
|
+
),
|
|
72
|
+
/* @__PURE__ */ y(Z, { fallback: null, children: r }),
|
|
73
|
+
/* @__PURE__ */ y(
|
|
74
|
+
J,
|
|
75
|
+
{
|
|
76
|
+
makeDefault: !0,
|
|
77
|
+
enabled: n.enabled,
|
|
78
|
+
enablePan: n.enablePan,
|
|
79
|
+
enableZoom: n.enableZoom,
|
|
80
|
+
enableRotate: n.enableRotate,
|
|
81
|
+
minDistance: n.minDistance,
|
|
82
|
+
maxDistance: n.maxDistance,
|
|
83
|
+
minPolarAngle: n.minPolarAngle,
|
|
84
|
+
maxPolarAngle: n.maxPolarAngle,
|
|
85
|
+
autoRotate: n.autoRotate,
|
|
86
|
+
autoRotateSpeed: n.autoRotateSpeed
|
|
87
|
+
}
|
|
88
|
+
),
|
|
89
|
+
f && /* @__PURE__ */ y(
|
|
90
|
+
K,
|
|
91
|
+
{
|
|
92
|
+
position: f.position,
|
|
93
|
+
opacity: f.opacity,
|
|
94
|
+
blur: f.blur
|
|
95
|
+
}
|
|
96
|
+
)
|
|
97
|
+
]
|
|
98
|
+
}
|
|
99
|
+
) });
|
|
100
|
+
}
|
|
101
|
+
function re({
|
|
102
|
+
url: r,
|
|
103
|
+
position: o = [0, 0, 0],
|
|
104
|
+
rotation: i = [0, 0, 0],
|
|
105
|
+
scale: t = 1,
|
|
106
|
+
onLoad: a,
|
|
107
|
+
onError: l
|
|
108
|
+
}) {
|
|
109
|
+
const { scene: c } = x(r), d = D(() => {
|
|
110
|
+
const m = c.clone(), e = [], n = /* @__PURE__ */ new Set(), u = [];
|
|
111
|
+
let f = 0;
|
|
112
|
+
return m.traverse((p) => {
|
|
113
|
+
if (f++, p.type === "Bone" && u.push(p.name), p.isMesh) {
|
|
114
|
+
const s = p;
|
|
115
|
+
e.push(s.name), s.castShadow = !0, s.receiveShadow = !0, (Array.isArray(s.material) ? s.material : [s.material]).forEach((A) => n.add(A.name));
|
|
116
|
+
}
|
|
117
|
+
}), a && setTimeout(() => {
|
|
118
|
+
a({
|
|
119
|
+
meshes: e.sort(),
|
|
120
|
+
materials: Array.from(n).sort(),
|
|
121
|
+
bones: u.sort(),
|
|
122
|
+
nodeCount: f
|
|
123
|
+
});
|
|
124
|
+
}, 0), m;
|
|
125
|
+
}, [c, a]);
|
|
126
|
+
return /* @__PURE__ */ y("group", { position: o, rotation: i, scale: typeof t == "number" ? [t, t, t] : t, children: /* @__PURE__ */ y("primitive", { object: d }) });
|
|
127
|
+
}
|
|
128
|
+
re.preload = (r) => {
|
|
129
|
+
x.preload(r);
|
|
130
|
+
};
|
|
131
|
+
function oe(r, o, i) {
|
|
132
|
+
const { actions: t, names: a } = Q(r, o), l = R(null), c = R(i == null ? void 0 : i.defaultAnimation);
|
|
133
|
+
M(() => {
|
|
134
|
+
if (a.length === 0) return;
|
|
135
|
+
const e = c.current;
|
|
136
|
+
let n = a[0];
|
|
137
|
+
if (e) {
|
|
138
|
+
const f = a.find((p) => p === e || p.includes(e));
|
|
139
|
+
f && (n = f);
|
|
140
|
+
}
|
|
141
|
+
const u = t[n];
|
|
142
|
+
u && (u.reset().fadeIn(0.5).play(), l.current = u);
|
|
143
|
+
}, [t, a]);
|
|
144
|
+
const d = T(
|
|
145
|
+
(e, n) => {
|
|
146
|
+
const {
|
|
147
|
+
loop: u = !1,
|
|
148
|
+
crossFadeDuration: f = 0.2,
|
|
149
|
+
restoreDefault: p = !0
|
|
150
|
+
} = n || {};
|
|
151
|
+
let s = t[e];
|
|
152
|
+
if (!s) {
|
|
153
|
+
const A = Object.keys(t).find(
|
|
154
|
+
(S) => S.toLowerCase().includes(e.toLowerCase()) || e.toLowerCase().includes(S.toLowerCase())
|
|
155
|
+
);
|
|
156
|
+
A && (s = t[A]);
|
|
157
|
+
}
|
|
158
|
+
if (!s) {
|
|
159
|
+
console.warn(`Animation "${e}" not found. Available: ${a.join(", ")}`);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const g = l.current;
|
|
163
|
+
if (!(g === s && s.isRunning()) && (g && g !== s && g.fadeOut(f), s.reset(), s.fadeIn(f), s.setLoop(
|
|
164
|
+
u ? E.LoopRepeat : E.LoopOnce,
|
|
165
|
+
u ? 1 / 0 : 1
|
|
166
|
+
), s.clampWhenFinished = !u, s.play(), u || s.getMixer().update(0), l.current = s, p && !u && c.current)) {
|
|
167
|
+
const A = s.getMixer(), S = (v) => {
|
|
168
|
+
if (v.action === s) {
|
|
169
|
+
A.removeEventListener("finished", S);
|
|
170
|
+
const b = t[c.current];
|
|
171
|
+
b && (s.fadeOut(f), b.reset().fadeIn(f).play(), l.current = b);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
A.addEventListener("finished", S);
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
[t, a]
|
|
178
|
+
), h = T(() => {
|
|
179
|
+
var e;
|
|
180
|
+
(e = l.current) == null || e.fadeOut(0.2), l.current = null;
|
|
181
|
+
}, []), m = T(() => a, [a]);
|
|
182
|
+
return {
|
|
183
|
+
playAnimation: d,
|
|
184
|
+
stopAnimation: h,
|
|
185
|
+
getAnimationNames: m,
|
|
186
|
+
actions: t
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
function H(r, o) {
|
|
190
|
+
const i = R(o || {}), t = R([]), a = R([]);
|
|
191
|
+
M(() => {
|
|
192
|
+
const h = /* @__PURE__ */ new Set(), m = [];
|
|
193
|
+
r.traverse((e) => {
|
|
194
|
+
e instanceof E.Mesh && e.morphTargetDictionary && e.morphTargetInfluences && (m.push(e), Object.keys(e.morphTargetDictionary).forEach((n) => {
|
|
195
|
+
h.add(n);
|
|
196
|
+
}));
|
|
197
|
+
}), t.current = Array.from(h).sort(), a.current = m;
|
|
198
|
+
}, [r]), Y(() => {
|
|
199
|
+
const h = i.current;
|
|
200
|
+
a.current.forEach((m) => {
|
|
201
|
+
!m.morphTargetDictionary || !m.morphTargetInfluences || Object.entries(h).forEach(([e, n]) => {
|
|
202
|
+
const u = m.morphTargetDictionary[e];
|
|
203
|
+
u !== void 0 && (m.morphTargetInfluences[u] = n);
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
}), M(() => {
|
|
207
|
+
o && (i.current = { ...o });
|
|
208
|
+
}, [o]);
|
|
209
|
+
const l = T((h, m) => {
|
|
210
|
+
i.current[h] = Math.max(0, Math.min(1, m));
|
|
211
|
+
}, []), c = T(() => t.current, []), d = T(() => ({ ...i.current }), []);
|
|
212
|
+
return {
|
|
213
|
+
setMorphTarget: l,
|
|
214
|
+
getMorphTargetNames: c,
|
|
215
|
+
getMorphTargetValues: d
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const U = q(null);
|
|
219
|
+
function ae() {
|
|
220
|
+
const r = z(U);
|
|
221
|
+
if (!r)
|
|
222
|
+
throw new Error("BoneAttachment must be used within an AnimatedModel");
|
|
223
|
+
return r;
|
|
224
|
+
}
|
|
225
|
+
const V = G(
|
|
226
|
+
({
|
|
227
|
+
url: r,
|
|
228
|
+
position: o = [0, 0, 0],
|
|
229
|
+
rotation: i = [0, 0, 0],
|
|
230
|
+
scale: t = 1,
|
|
231
|
+
defaultAnimation: a,
|
|
232
|
+
morphTargets: l,
|
|
233
|
+
onLoad: c,
|
|
234
|
+
onError: d,
|
|
235
|
+
children: h
|
|
236
|
+
}, m) => {
|
|
237
|
+
const e = R(null), n = R([]), { scene: u, animations: f } = x(r), p = D(() => _.clone(u), [u]), { playAnimation: s, stopAnimation: g, getAnimationNames: A } = oe(
|
|
238
|
+
f,
|
|
239
|
+
p,
|
|
240
|
+
{ defaultAnimation: a }
|
|
241
|
+
), { setMorphTarget: S } = H(
|
|
242
|
+
p,
|
|
243
|
+
l
|
|
244
|
+
);
|
|
245
|
+
M(() => {
|
|
246
|
+
if (!p) return;
|
|
247
|
+
const j = setTimeout(() => {
|
|
248
|
+
const P = [], N = [], O = /* @__PURE__ */ new Set(), I = /* @__PURE__ */ new Set();
|
|
249
|
+
let k = 0;
|
|
250
|
+
p.traverse((C) => {
|
|
251
|
+
if (k++, C.type === "Bone" && P.push(C.name), C.isMesh) {
|
|
252
|
+
const w = C;
|
|
253
|
+
N.push(w.name), w.castShadow = !0, w.receiveShadow = !0, (Array.isArray(w.material) ? w.material : [w.material]).forEach((B) => {
|
|
254
|
+
O.add(B.name), B.shadowSide = E.DoubleSide;
|
|
255
|
+
}), w.morphTargetDictionary && Object.keys(w.morphTargetDictionary).forEach((B) => {
|
|
256
|
+
I.add(B);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}), n.current = Array.from(I).sort(), c == null || c({
|
|
260
|
+
meshes: N.sort(),
|
|
261
|
+
materials: Array.from(O).sort(),
|
|
262
|
+
bones: P.sort(),
|
|
263
|
+
nodeCount: k,
|
|
264
|
+
animations: f.map((C) => C.name),
|
|
265
|
+
morphTargetNames: n.current
|
|
266
|
+
});
|
|
267
|
+
}, 0);
|
|
268
|
+
return () => clearTimeout(j);
|
|
269
|
+
}, [p, f, c]), W(m, () => ({
|
|
270
|
+
playAnimation: s,
|
|
271
|
+
stopAnimation: g,
|
|
272
|
+
getAnimationNames: A,
|
|
273
|
+
getGroup: () => e.current,
|
|
274
|
+
setMorphTarget: S,
|
|
275
|
+
getMorphTargetNames: () => n.current
|
|
276
|
+
}));
|
|
277
|
+
const v = D(
|
|
278
|
+
() => ({
|
|
279
|
+
scene: p,
|
|
280
|
+
getBone: (j) => p.getObjectByName(j) || null
|
|
281
|
+
}),
|
|
282
|
+
[p]
|
|
283
|
+
), b = typeof t == "number" ? [t, t, t] : t;
|
|
284
|
+
return /* @__PURE__ */ y(U.Provider, { value: v, children: /* @__PURE__ */ $(
|
|
285
|
+
"group",
|
|
286
|
+
{
|
|
287
|
+
ref: e,
|
|
288
|
+
position: o,
|
|
289
|
+
rotation: i,
|
|
290
|
+
scale: b,
|
|
291
|
+
children: [
|
|
292
|
+
/* @__PURE__ */ y("primitive", { object: p }),
|
|
293
|
+
h
|
|
294
|
+
]
|
|
295
|
+
}
|
|
296
|
+
) });
|
|
297
|
+
}
|
|
298
|
+
);
|
|
299
|
+
V.displayName = "AnimatedModel";
|
|
300
|
+
V.preload = (r) => {
|
|
301
|
+
x.preload(r);
|
|
302
|
+
};
|
|
303
|
+
const se = G(
|
|
304
|
+
({
|
|
305
|
+
url: r,
|
|
306
|
+
position: o = [0, 0, 0],
|
|
307
|
+
rotation: i = [0, 0, 0],
|
|
308
|
+
scale: t = 1,
|
|
309
|
+
morphTargets: a,
|
|
310
|
+
onMorphTargetsFound: l,
|
|
311
|
+
onLoad: c,
|
|
312
|
+
onError: d
|
|
313
|
+
}, h) => {
|
|
314
|
+
const { scene: m } = x(r), e = D(() => m.clone(), [m]), { setMorphTarget: n, getMorphTargetNames: u, getMorphTargetValues: f } = H(
|
|
315
|
+
e,
|
|
316
|
+
a
|
|
317
|
+
);
|
|
318
|
+
return M(() => {
|
|
319
|
+
const s = u();
|
|
320
|
+
s.length > 0 && (l == null || l(s));
|
|
321
|
+
}, [e, u, l]), M(() => {
|
|
322
|
+
if (!e) return;
|
|
323
|
+
const s = [], g = /* @__PURE__ */ new Set(), A = [];
|
|
324
|
+
let S = 0;
|
|
325
|
+
e.traverse((v) => {
|
|
326
|
+
if (S++, v.type === "Bone" && A.push(v.name), v.isMesh) {
|
|
327
|
+
const b = v;
|
|
328
|
+
s.push(b.name), b.castShadow = !0, b.receiveShadow = !0, (Array.isArray(b.material) ? b.material : [b.material]).forEach((P) => g.add(P.name));
|
|
329
|
+
}
|
|
330
|
+
}), c == null || c({
|
|
331
|
+
meshes: s.sort(),
|
|
332
|
+
materials: Array.from(g).sort(),
|
|
333
|
+
bones: A.sort(),
|
|
334
|
+
nodeCount: S
|
|
335
|
+
});
|
|
336
|
+
}, [e, c]), W(h, () => ({
|
|
337
|
+
setMorphTarget: n,
|
|
338
|
+
getMorphTargetNames: u,
|
|
339
|
+
getMorphTargetValues: f
|
|
340
|
+
})), /* @__PURE__ */ y("group", { position: o, rotation: i, scale: typeof t == "number" ? [t, t, t] : t, children: /* @__PURE__ */ y("primitive", { object: e }) });
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
se.displayName = "MorphableModel";
|
|
344
|
+
function pe({
|
|
345
|
+
children: r,
|
|
346
|
+
bone: o,
|
|
347
|
+
position: i = [0, 0, 0],
|
|
348
|
+
rotation: t = [0, 0, 0],
|
|
349
|
+
scale: a = 1
|
|
350
|
+
}) {
|
|
351
|
+
const { getBone: l } = ae(), [c, d] = F(null);
|
|
352
|
+
return M(() => {
|
|
353
|
+
const m = l(o);
|
|
354
|
+
m ? d(m) : console.warn(`Bone "${o}" not found in model`);
|
|
355
|
+
}, [o, l]), c ? ee(
|
|
356
|
+
/* @__PURE__ */ y(
|
|
357
|
+
"group",
|
|
358
|
+
{
|
|
359
|
+
position: i,
|
|
360
|
+
rotation: t,
|
|
361
|
+
scale: typeof a == "number" ? [a, a, a] : a,
|
|
362
|
+
children: r
|
|
363
|
+
}
|
|
364
|
+
),
|
|
365
|
+
c
|
|
366
|
+
) : null;
|
|
367
|
+
}
|
|
368
|
+
function he(r, o) {
|
|
369
|
+
const { scene: i, animations: t } = x(r), a = D(() => _.clone(i), [i]);
|
|
370
|
+
return M(() => {
|
|
371
|
+
var m;
|
|
372
|
+
if (!a) return;
|
|
373
|
+
const l = [], c = [], d = /* @__PURE__ */ new Set();
|
|
374
|
+
let h = 0;
|
|
375
|
+
a.traverse((e) => {
|
|
376
|
+
if (h++, e.type === "Bone" && l.push(e.name), e.isMesh) {
|
|
377
|
+
const n = e;
|
|
378
|
+
c.push(n.name), n.castShadow = !0, n.receiveShadow = !0, (Array.isArray(n.material) ? n.material : [n.material]).forEach((f) => {
|
|
379
|
+
d.add(f.name), f.shadowSide = E.DoubleSide;
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
}), (m = o == null ? void 0 : o.onLoad) == null || m.call(o, {
|
|
383
|
+
meshes: c.sort(),
|
|
384
|
+
materials: Array.from(d).sort(),
|
|
385
|
+
bones: l.sort(),
|
|
386
|
+
nodeCount: h
|
|
387
|
+
});
|
|
388
|
+
}, [a, o]), { scene: a, animations: t };
|
|
389
|
+
}
|
|
390
|
+
function de(r) {
|
|
391
|
+
x.preload(r);
|
|
392
|
+
}
|
|
393
|
+
export {
|
|
394
|
+
V as AnimatedModel,
|
|
395
|
+
pe as BoneAttachment,
|
|
396
|
+
re as Model,
|
|
397
|
+
se as MorphableModel,
|
|
398
|
+
fe as Scene3D,
|
|
399
|
+
de as preloadModel,
|
|
400
|
+
oe as useAnimationController,
|
|
401
|
+
he as useClonedModel,
|
|
402
|
+
H as useMorphTargets
|
|
403
|
+
};
|
|
404
|
+
//# sourceMappingURL=mbt-3d.js.map
|