@guoquan.net/flow-engine 0.1.2 → 0.1.10
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 +75 -37
- package/dist/assets/avatars/expressive/config.json +7 -0
- package/dist/core/FlowEngine.d.ts +10 -3
- package/dist/core/LookAtProcessor.d.ts +45 -0
- package/dist/flow.es.js +723 -98
- package/dist/flow.umd.js +1 -1
- package/dist/main.d.ts +0 -0
- package/dist/types/index.d.ts +20 -0
- package/package.json +8 -3
package/dist/flow.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(l,p){typeof exports=="object"&&typeof module<"u"?p(exports,require("three"),require("three/webgpu"),require("three/examples/jsm/controls/OrbitControls.js"),require("three/examples/jsm/loaders/GLTFLoader.js")):typeof define=="function"&&define.amd?define(["exports","three","three/webgpu","three/examples/jsm/controls/OrbitControls.js","three/examples/jsm/loaders/GLTFLoader.js"],p):(l=typeof globalThis<"u"?globalThis:l||self,p(l.Flow={},l.THREE,l.THREE,l.OrbitControls_js,l.GLTFLoader_js))})(this,(function(l,p,S,C,y){"use strict";function A(d){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(d){for(const e in d)if(e!=="default"){const o=Object.getOwnPropertyDescriptor(d,e);Object.defineProperty(t,e,o.get?o:{enumerable:!0,get:()=>d[e]})}}return t.default=d,Object.freeze(t)}const i=A(p);class M{loader;constructor(){this.loader=new y.GLTFLoader}async load(t){console.log(`[Flow] Loading avatar config from: ${t}`);try{const e=await fetch(t);if(!e.ok)throw new Error(`Failed to load config: ${e.statusText}`);const o=await e.json(),n=t.substring(0,t.lastIndexOf("/")+1)+o.modelSrc;console.log(`[Flow] Config loaded. Loading model from: ${n}`);let r,a=[];try{const c=await this.loader.loadAsync(n);r=c.scene,a=c.animations||[]}catch(c){console.warn(`[Flow] Failed to load 3D model at ${n}. Using fallback placeholder.`,c),r=this.createFallbackAvatar()}return this.applyConfig(r,o),{model:r,config:o,animations:a}}catch(e){throw console.error("[Flow] Error loading avatar:",e),e}}applyConfig(t,e){e.scale&&t.scale.setScalar(e.scale),e.initialPosition&&t.position.set(...e.initialPosition),t.traverse(o=>{o.isMesh&&(o.castShadow=!0,o.receiveShadow=!0)})}createFallbackAvatar(){const t=new i.Group,e=new i.MeshStandardMaterial({color:54015,roughness:.3,metalness:.8}),o=new i.BoxGeometry(.8,.9,.8),s=new i.Mesh(o,e);s.position.y=1.5,s.name="Head",t.add(s);const n=new i.SphereGeometry(.1),r=new i.MeshBasicMaterial({color:16777215}),a=new i.Mesh(n,r);a.position.set(-.2,1.5,.4),t.add(a);const c=new i.Mesh(n,r);c.position.set(.2,1.5,.4),t.add(c);const w=new i.CylinderGeometry(.6,.4,1.5,8),h=new i.Mesh(w,e);h.position.y=.5,h.name="Body",t.add(h);const m=new i.CapsuleGeometry(.15,1),u=new i.Mesh(m,e);u.position.set(-.9,.8,0),u.rotation.z=Math.PI/4,u.name="LeftArm",t.add(u);const f=new i.Mesh(m,e);return f.position.set(.9,.8,0),f.rotation.z=-Math.PI/4,f.name="RightArm",t.add(f),t}}class L{loader;constructor(){this.loader=new y.GLTFLoader}async load(t){try{const e=await fetch(t);if(!e.ok)throw new Error("Failed to load stage config");const o=await e.json();let s,n=[];if(o.modelSrc){const a=t.substring(0,t.lastIndexOf("/")+1)+o.modelSrc,c=await this.loader.loadAsync(a);s=c.scene,n=c.animations||[]}else s=this.createProceduralStage();return this.applyConfig(s,o),{model:s,config:o,animations:n}}catch(e){throw console.error("[Flow] Stage load error:",e),e}}applyConfig(t,e){e.scale&&t.scale.setScalar(e.scale),e.position&&t.position.set(...e.position),e.rotation&&t.rotation.set(...e.rotation),t.traverse(o=>{o.isMesh&&(o.receiveShadow=!0,o.castShadow=!0)})}createProceduralStage(){const t=new i.Group,e=new i.CylinderGeometry(2,2.2,.2,32),o=new i.MeshStandardMaterial({color:2236962,roughness:.2,metalness:.8}),s=new i.Mesh(e,o);s.position.y=-.1,t.add(s);const n=new i.TorusGeometry(1.8,.05,16,100),r=new i.MeshBasicMaterial({color:54015}),a=new i.Mesh(n,r);a.rotation.x=-Math.PI/2,a.position.y=.01,t.add(a);const c=new i.BoxGeometry(.5,.05,.5);for(let w=0;w<4;w++){const h=new i.Mesh(c,o),m=w/4*Math.PI*2;h.position.set(Math.cos(m)*2.5,0,Math.sin(m)*2.5),h.lookAt(0,0,0),t.add(h)}return t}}class g{mixer;clips;states={};activeActions=new Map;currentState=null;defaultState="idle";constructor(t,e){this.mixer=new i.AnimationMixer(t),this.clips=e,this.mixer.addEventListener("finished",this.onFinished.bind(this))}init(t){this.defaultState=t.defaultState,this.states=t.states,Object.entries(this.states).forEach(([e,o])=>{this.findClip(o.clipName)||console.warn(`[AnimationController] Clip "${o.clipName}" for state "${e}" not found.`)}),this.play(this.defaultState)}update(t){this.mixer.update(t)}play(t,e=!1){const o=this.states[t];if(!o){console.warn(`[AnimationController] State "${t}" not defined.`);return}const s=this.findClip(o.clipName);if(!s||this.currentState===t&&!e)return;const n=this.mixer.clipAction(s);n.setLoop(o.loop?i.LoopRepeat:i.LoopOnce,o.loop?1/0:1),n.clampWhenFinished=!o.loop,n.timeScale=o.timeScale??1;const r=o.fadeDuration??.3;if(this.currentState){const a=this.activeActions.get(this.currentState);a&&a!==n?(a.fadeOut(r),n.reset(),n.fadeIn(r),n.play()):n.reset().play()}else n.reset().play();this.activeActions.set(t,n),this.currentState=t,console.log(`[Anim] Transition to: ${t} (Loop: ${o.loop})`)}onFinished(t){if(this.currentState&&this.activeActions.get(this.currentState)===t.action){const e=this.states[this.currentState],o=()=>{e&&e.next?this.play(e.next):!e.loop&&this.currentState!==this.defaultState&&this.play(this.defaultState)};e.holdDuration&&e.holdDuration>0?setTimeout(o,e.holdDuration*1e3):o()}}findClip(t){let e=this.clips.find(o=>o.name===t);return e||(e=this.clips.find(o=>o.name.toLowerCase()===t.toLowerCase())),e||(e=this.clips.find(o=>o.name.toLowerCase().includes(t.toLowerCase()))),e}}class b{container;scene;camera;renderer;controls;clock;loader;stageLoader;avatarModel=null;stageModel=null;animController=null;stageAnimController=null;constructor(t){const e=document.getElementById(t);if(!e)throw new Error(`Container #${t} not found`);this.container=e,this.clock=new i.Clock,this.loader=new M,this.stageLoader=new L,this.scene=new i.Scene,this.scene.background=new i.Color(1710618),this.scene.fog=new i.Fog(1710618,10,50),this.camera=new i.PerspectiveCamera(45,window.innerWidth/window.innerHeight,.1,100),this.camera.position.set(0,1.5,5),this.renderer=new S.WebGPURenderer({antialias:!0,alpha:!0}),this.renderer.setSize(window.innerWidth,window.innerHeight),this.renderer.setPixelRatio(window.devicePixelRatio),this.container.appendChild(this.renderer.domElement),this.setupLights(),this.controls=new C.OrbitControls(this.camera,this.renderer.domElement),this.controls.enableDamping=!0,this.controls.target.set(0,1,0),window.addEventListener("resize",this.onWindowResize.bind(this)),this.renderer.setAnimationLoop(this.animate.bind(this))}setupLights(){const t=new i.AmbientLight(16777215,.6);this.scene.add(t);const e=new i.DirectionalLight(16777215,1);e.position.set(5,10,7),e.castShadow=!0,this.scene.add(e);const o=new i.SpotLight(54015,5);o.position.set(-5,5,-5),o.lookAt(0,1,0),this.scene.add(o)}async loadAvatar(t){this.avatarModel&&this.scene.remove(this.avatarModel);const{model:e,config:o,animations:s}=await this.loader.load(t);if(this.avatarModel=e,this.scene.add(this.avatarModel),s.length>0){this.animController=new g(this.avatarModel,s);const n=o.animations||{defaultState:"idle",states:{idle:{clipName:"Idle",loop:!0},wave:{clipName:"Wave",loop:!1,next:"idle"},dance:{clipName:"Dance",loop:!1,next:"idle"},bow:{clipName:"Bow",loop:!1,next:"idle"},walk:{clipName:"Walking",loop:!0}}};this.animController.init(n)}console.log(`[Flow] Avatar "${o.name}" loaded successfully.`)}async loadStage(t){this.stageModel&&this.scene.remove(this.stageModel);const{model:e,config:o,animations:s}=await this.stageLoader.load(t);this.stageModel=e,this.scene.add(this.stageModel),s.length>0&&o.animations&&(this.stageAnimController=new g(this.stageModel,s),this.stageAnimController.init(o.animations)),console.log(`[Flow] Stage "${o.name}" loaded successfully.`)}onWindowResize(){this.camera.aspect=window.innerWidth/window.innerHeight,this.camera.updateProjectionMatrix(),this.renderer.setSize(window.innerWidth,window.innerHeight)}isAutoRotate=!1;playAction(t){if(console.log(`[FlowEngine] Playing action: ${t}`),this.animController){this.animController.play(t.toLowerCase());return}}animate(t){const e=this.clock.getDelta();this.avatarModel&&this.animController&&this.animController.update(e),this.stageModel&&this.stageAnimController&&this.stageAnimController.update(e),this.controls.autoRotate=this.isAutoRotate,this.controls.update(),this.renderer.render(this.scene,this.camera)}}l.AnimationController=g,l.AvatarLoader=M,l.FlowEngine=b,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})}));
|
|
1
|
+
(function(_,r){typeof exports=="object"&&typeof module<"u"?r(exports,require("three"),require("three/webgpu"),require("three/examples/jsm/loaders/GLTFLoader.js")):typeof define=="function"&&define.amd?define(["exports","three","three/webgpu","three/examples/jsm/loaders/GLTFLoader.js"],r):(_=typeof globalThis<"u"?globalThis:_||self,r(_.Flow={},_.THREE,_.THREE,_.GLTFLoader_js))})(this,(function(_,r,k,S){"use strict";function E(o){const t=Object.create(null,{[Symbol.toStringTag]:{value:"Module"}});if(o){for(const e in o)if(e!=="default"){const i=Object.getOwnPropertyDescriptor(o,e);Object.defineProperty(t,e,i.get?i:{enumerable:!0,get:()=>o[e]})}}return t.default=o,Object.freeze(t)}const n=E(r),L={type:"change"},M={type:"start"},O={type:"end"},b=new r.Ray,T=new r.Plane,v=Math.cos(70*r.MathUtils.DEG2RAD),p=new r.Vector3,u=2*Math.PI,c={NONE:-1,ROTATE:0,DOLLY:1,PAN:2,TOUCH_ROTATE:3,TOUCH_PAN:4,TOUCH_DOLLY_PAN:5,TOUCH_DOLLY_ROTATE:6},D=1e-6;class x extends r.Controls{constructor(t,e=null){super(t,e),this.state=c.NONE,this.target=new r.Vector3,this.cursor=new r.Vector3,this.minDistance=0,this.maxDistance=1/0,this.minZoom=0,this.maxZoom=1/0,this.minTargetRadius=0,this.maxTargetRadius=1/0,this.minPolarAngle=0,this.maxPolarAngle=Math.PI,this.minAzimuthAngle=-1/0,this.maxAzimuthAngle=1/0,this.enableDamping=!1,this.dampingFactor=.05,this.enableZoom=!0,this.zoomSpeed=1,this.enableRotate=!0,this.rotateSpeed=1,this.keyRotateSpeed=1,this.enablePan=!0,this.panSpeed=1,this.screenSpacePanning=!0,this.keyPanSpeed=7,this.zoomToCursor=!1,this.autoRotate=!1,this.autoRotateSpeed=2,this.keys={LEFT:"ArrowLeft",UP:"ArrowUp",RIGHT:"ArrowRight",BOTTOM:"ArrowDown"},this.mouseButtons={LEFT:r.MOUSE.ROTATE,MIDDLE:r.MOUSE.DOLLY,RIGHT:r.MOUSE.PAN},this.touches={ONE:r.TOUCH.ROTATE,TWO:r.TOUCH.DOLLY_PAN},this.target0=this.target.clone(),this.position0=this.object.position.clone(),this.zoom0=this.object.zoom,this._domElementKeyEvents=null,this._lastPosition=new r.Vector3,this._lastQuaternion=new r.Quaternion,this._lastTargetPosition=new r.Vector3,this._quat=new r.Quaternion().setFromUnitVectors(t.up,new r.Vector3(0,1,0)),this._quatInverse=this._quat.clone().invert(),this._spherical=new r.Spherical,this._sphericalDelta=new r.Spherical,this._scale=1,this._panOffset=new r.Vector3,this._rotateStart=new r.Vector2,this._rotateEnd=new r.Vector2,this._rotateDelta=new r.Vector2,this._panStart=new r.Vector2,this._panEnd=new r.Vector2,this._panDelta=new r.Vector2,this._dollyStart=new r.Vector2,this._dollyEnd=new r.Vector2,this._dollyDelta=new r.Vector2,this._dollyDirection=new r.Vector3,this._mouse=new r.Vector2,this._performCursorZoom=!1,this._pointers=[],this._pointerPositions={},this._controlActive=!1,this._onPointerMove=N.bind(this),this._onPointerDown=j.bind(this),this._onPointerUp=I.bind(this),this._onContextMenu=z.bind(this),this._onMouseWheel=F.bind(this),this._onKeyDown=Y.bind(this),this._onTouchStart=G.bind(this),this._onTouchMove=V.bind(this),this._onMouseDown=R.bind(this),this._onMouseMove=U.bind(this),this._interceptControlDown=K.bind(this),this._interceptControlUp=W.bind(this),this.domElement!==null&&this.connect(this.domElement),this.update()}connect(t){super.connect(t),this.domElement.addEventListener("pointerdown",this._onPointerDown),this.domElement.addEventListener("pointercancel",this._onPointerUp),this.domElement.addEventListener("contextmenu",this._onContextMenu),this.domElement.addEventListener("wheel",this._onMouseWheel,{passive:!1}),this.domElement.getRootNode().addEventListener("keydown",this._interceptControlDown,{passive:!0,capture:!0}),this.domElement.style.touchAction="none"}disconnect(){this.domElement.removeEventListener("pointerdown",this._onPointerDown),this.domElement.ownerDocument.removeEventListener("pointermove",this._onPointerMove),this.domElement.ownerDocument.removeEventListener("pointerup",this._onPointerUp),this.domElement.removeEventListener("pointercancel",this._onPointerUp),this.domElement.removeEventListener("wheel",this._onMouseWheel),this.domElement.removeEventListener("contextmenu",this._onContextMenu),this.stopListenToKeyEvents(),this.domElement.getRootNode().removeEventListener("keydown",this._interceptControlDown,{capture:!0}),this.domElement.style.touchAction="auto"}dispose(){this.disconnect()}getPolarAngle(){return this._spherical.phi}getAzimuthalAngle(){return this._spherical.theta}getDistance(){return this.object.position.distanceTo(this.target)}listenToKeyEvents(t){t.addEventListener("keydown",this._onKeyDown),this._domElementKeyEvents=t}stopListenToKeyEvents(){this._domElementKeyEvents!==null&&(this._domElementKeyEvents.removeEventListener("keydown",this._onKeyDown),this._domElementKeyEvents=null)}saveState(){this.target0.copy(this.target),this.position0.copy(this.object.position),this.zoom0=this.object.zoom}reset(){this.target.copy(this.target0),this.object.position.copy(this.position0),this.object.zoom=this.zoom0,this.object.updateProjectionMatrix(),this.dispatchEvent(L),this.update(),this.state=c.NONE}update(t=null){const e=this.object.position;p.copy(e).sub(this.target),p.applyQuaternion(this._quat),this._spherical.setFromVector3(p),this.autoRotate&&this.state===c.NONE&&this._rotateLeft(this._getAutoRotationAngle(t)),this.enableDamping?(this._spherical.theta+=this._sphericalDelta.theta*this.dampingFactor,this._spherical.phi+=this._sphericalDelta.phi*this.dampingFactor):(this._spherical.theta+=this._sphericalDelta.theta,this._spherical.phi+=this._sphericalDelta.phi);let i=this.minAzimuthAngle,s=this.maxAzimuthAngle;isFinite(i)&&isFinite(s)&&(i<-Math.PI?i+=u:i>Math.PI&&(i-=u),s<-Math.PI?s+=u:s>Math.PI&&(s-=u),i<=s?this._spherical.theta=Math.max(i,Math.min(s,this._spherical.theta)):this._spherical.theta=this._spherical.theta>(i+s)/2?Math.max(i,this._spherical.theta):Math.min(s,this._spherical.theta)),this._spherical.phi=Math.max(this.minPolarAngle,Math.min(this.maxPolarAngle,this._spherical.phi)),this._spherical.makeSafe(),this.enableDamping===!0?this.target.addScaledVector(this._panOffset,this.dampingFactor):this.target.add(this._panOffset),this.target.sub(this.cursor),this.target.clampLength(this.minTargetRadius,this.maxTargetRadius),this.target.add(this.cursor);let a=!1;if(this.zoomToCursor&&this._performCursorZoom||this.object.isOrthographicCamera)this._spherical.radius=this._clampDistance(this._spherical.radius);else{const h=this._spherical.radius;this._spherical.radius=this._clampDistance(this._spherical.radius*this._scale),a=h!=this._spherical.radius}if(p.setFromSpherical(this._spherical),p.applyQuaternion(this._quatInverse),e.copy(this.target).add(p),this.object.lookAt(this.target),this.enableDamping===!0?(this._sphericalDelta.theta*=1-this.dampingFactor,this._sphericalDelta.phi*=1-this.dampingFactor,this._panOffset.multiplyScalar(1-this.dampingFactor)):(this._sphericalDelta.set(0,0,0),this._panOffset.set(0,0,0)),this.zoomToCursor&&this._performCursorZoom){let h=null;if(this.object.isPerspectiveCamera){const l=p.length();h=this._clampDistance(l*this._scale);const d=l-h;this.object.position.addScaledVector(this._dollyDirection,d),this.object.updateMatrixWorld(),a=!!d}else if(this.object.isOrthographicCamera){const l=new r.Vector3(this._mouse.x,this._mouse.y,0);l.unproject(this.object);const d=this.object.zoom;this.object.zoom=Math.max(this.minZoom,Math.min(this.maxZoom,this.object.zoom/this._scale)),this.object.updateProjectionMatrix(),a=d!==this.object.zoom;const f=new r.Vector3(this._mouse.x,this._mouse.y,0);f.unproject(this.object),this.object.position.sub(f).add(l),this.object.updateMatrixWorld(),h=p.length()}else console.warn("WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled."),this.zoomToCursor=!1;h!==null&&(this.screenSpacePanning?this.target.set(0,0,-1).transformDirection(this.object.matrix).multiplyScalar(h).add(this.object.position):(b.origin.copy(this.object.position),b.direction.set(0,0,-1).transformDirection(this.object.matrix),Math.abs(this.object.up.dot(b.direction))<v?this.object.lookAt(this.target):(T.setFromNormalAndCoplanarPoint(this.object.up,this.target),b.intersectPlane(T,this.target))))}else if(this.object.isOrthographicCamera){const h=this.object.zoom;this.object.zoom=Math.max(this.minZoom,Math.min(this.maxZoom,this.object.zoom/this._scale)),h!==this.object.zoom&&(this.object.updateProjectionMatrix(),a=!0)}return this._scale=1,this._performCursorZoom=!1,a||this._lastPosition.distanceToSquared(this.object.position)>D||8*(1-this._lastQuaternion.dot(this.object.quaternion))>D||this._lastTargetPosition.distanceToSquared(this.target)>D?(this.dispatchEvent(L),this._lastPosition.copy(this.object.position),this._lastQuaternion.copy(this.object.quaternion),this._lastTargetPosition.copy(this.target),!0):!1}_getAutoRotationAngle(t){return t!==null?u/60*this.autoRotateSpeed*t:u/60/60*this.autoRotateSpeed}_getZoomScale(t){const e=Math.abs(t*.01);return Math.pow(.95,this.zoomSpeed*e)}_rotateLeft(t){this._sphericalDelta.theta-=t}_rotateUp(t){this._sphericalDelta.phi-=t}_panLeft(t,e){p.setFromMatrixColumn(e,0),p.multiplyScalar(-t),this._panOffset.add(p)}_panUp(t,e){this.screenSpacePanning===!0?p.setFromMatrixColumn(e,1):(p.setFromMatrixColumn(e,0),p.crossVectors(this.object.up,p)),p.multiplyScalar(t),this._panOffset.add(p)}_pan(t,e){const i=this.domElement;if(this.object.isPerspectiveCamera){const s=this.object.position;p.copy(s).sub(this.target);let a=p.length();a*=Math.tan(this.object.fov/2*Math.PI/180),this._panLeft(2*t*a/i.clientHeight,this.object.matrix),this._panUp(2*e*a/i.clientHeight,this.object.matrix)}else this.object.isOrthographicCamera?(this._panLeft(t*(this.object.right-this.object.left)/this.object.zoom/i.clientWidth,this.object.matrix),this._panUp(e*(this.object.top-this.object.bottom)/this.object.zoom/i.clientHeight,this.object.matrix)):(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - pan disabled."),this.enablePan=!1)}_dollyOut(t){this.object.isPerspectiveCamera||this.object.isOrthographicCamera?this._scale/=t:(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."),this.enableZoom=!1)}_dollyIn(t){this.object.isPerspectiveCamera||this.object.isOrthographicCamera?this._scale*=t:(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."),this.enableZoom=!1)}_updateZoomParameters(t,e){if(!this.zoomToCursor)return;this._performCursorZoom=!0;const i=this.domElement.getBoundingClientRect(),s=t-i.left,a=e-i.top,h=i.width,l=i.height;this._mouse.x=s/h*2-1,this._mouse.y=-(a/l)*2+1,this._dollyDirection.set(this._mouse.x,this._mouse.y,1).unproject(this.object).sub(this.object.position).normalize()}_clampDistance(t){return Math.max(this.minDistance,Math.min(this.maxDistance,t))}_handleMouseDownRotate(t){this._rotateStart.set(t.clientX,t.clientY)}_handleMouseDownDolly(t){this._updateZoomParameters(t.clientX,t.clientX),this._dollyStart.set(t.clientX,t.clientY)}_handleMouseDownPan(t){this._panStart.set(t.clientX,t.clientY)}_handleMouseMoveRotate(t){this._rotateEnd.set(t.clientX,t.clientY),this._rotateDelta.subVectors(this._rotateEnd,this._rotateStart).multiplyScalar(this.rotateSpeed);const e=this.domElement;this._rotateLeft(u*this._rotateDelta.x/e.clientHeight),this._rotateUp(u*this._rotateDelta.y/e.clientHeight),this._rotateStart.copy(this._rotateEnd),this.update()}_handleMouseMoveDolly(t){this._dollyEnd.set(t.clientX,t.clientY),this._dollyDelta.subVectors(this._dollyEnd,this._dollyStart),this._dollyDelta.y>0?this._dollyOut(this._getZoomScale(this._dollyDelta.y)):this._dollyDelta.y<0&&this._dollyIn(this._getZoomScale(this._dollyDelta.y)),this._dollyStart.copy(this._dollyEnd),this.update()}_handleMouseMovePan(t){this._panEnd.set(t.clientX,t.clientY),this._panDelta.subVectors(this._panEnd,this._panStart).multiplyScalar(this.panSpeed),this._pan(this._panDelta.x,this._panDelta.y),this._panStart.copy(this._panEnd),this.update()}_handleMouseWheel(t){this._updateZoomParameters(t.clientX,t.clientY),t.deltaY<0?this._dollyIn(this._getZoomScale(t.deltaY)):t.deltaY>0&&this._dollyOut(this._getZoomScale(t.deltaY)),this.update()}_handleKeyDown(t){let e=!1;switch(t.code){case this.keys.UP:t.ctrlKey||t.metaKey||t.shiftKey?this.enableRotate&&this._rotateUp(u*this.keyRotateSpeed/this.domElement.clientHeight):this.enablePan&&this._pan(0,this.keyPanSpeed),e=!0;break;case this.keys.BOTTOM:t.ctrlKey||t.metaKey||t.shiftKey?this.enableRotate&&this._rotateUp(-u*this.keyRotateSpeed/this.domElement.clientHeight):this.enablePan&&this._pan(0,-this.keyPanSpeed),e=!0;break;case this.keys.LEFT:t.ctrlKey||t.metaKey||t.shiftKey?this.enableRotate&&this._rotateLeft(u*this.keyRotateSpeed/this.domElement.clientHeight):this.enablePan&&this._pan(this.keyPanSpeed,0),e=!0;break;case this.keys.RIGHT:t.ctrlKey||t.metaKey||t.shiftKey?this.enableRotate&&this._rotateLeft(-u*this.keyRotateSpeed/this.domElement.clientHeight):this.enablePan&&this._pan(-this.keyPanSpeed,0),e=!0;break}e&&(t.preventDefault(),this.update())}_handleTouchStartRotate(t){if(this._pointers.length===1)this._rotateStart.set(t.pageX,t.pageY);else{const e=this._getSecondPointerPosition(t),i=.5*(t.pageX+e.x),s=.5*(t.pageY+e.y);this._rotateStart.set(i,s)}}_handleTouchStartPan(t){if(this._pointers.length===1)this._panStart.set(t.pageX,t.pageY);else{const e=this._getSecondPointerPosition(t),i=.5*(t.pageX+e.x),s=.5*(t.pageY+e.y);this._panStart.set(i,s)}}_handleTouchStartDolly(t){const e=this._getSecondPointerPosition(t),i=t.pageX-e.x,s=t.pageY-e.y,a=Math.sqrt(i*i+s*s);this._dollyStart.set(0,a)}_handleTouchStartDollyPan(t){this.enableZoom&&this._handleTouchStartDolly(t),this.enablePan&&this._handleTouchStartPan(t)}_handleTouchStartDollyRotate(t){this.enableZoom&&this._handleTouchStartDolly(t),this.enableRotate&&this._handleTouchStartRotate(t)}_handleTouchMoveRotate(t){if(this._pointers.length==1)this._rotateEnd.set(t.pageX,t.pageY);else{const i=this._getSecondPointerPosition(t),s=.5*(t.pageX+i.x),a=.5*(t.pageY+i.y);this._rotateEnd.set(s,a)}this._rotateDelta.subVectors(this._rotateEnd,this._rotateStart).multiplyScalar(this.rotateSpeed);const e=this.domElement;this._rotateLeft(u*this._rotateDelta.x/e.clientHeight),this._rotateUp(u*this._rotateDelta.y/e.clientHeight),this._rotateStart.copy(this._rotateEnd)}_handleTouchMovePan(t){if(this._pointers.length===1)this._panEnd.set(t.pageX,t.pageY);else{const e=this._getSecondPointerPosition(t),i=.5*(t.pageX+e.x),s=.5*(t.pageY+e.y);this._panEnd.set(i,s)}this._panDelta.subVectors(this._panEnd,this._panStart).multiplyScalar(this.panSpeed),this._pan(this._panDelta.x,this._panDelta.y),this._panStart.copy(this._panEnd)}_handleTouchMoveDolly(t){const e=this._getSecondPointerPosition(t),i=t.pageX-e.x,s=t.pageY-e.y,a=Math.sqrt(i*i+s*s);this._dollyEnd.set(0,a),this._dollyDelta.set(0,Math.pow(this._dollyEnd.y/this._dollyStart.y,this.zoomSpeed)),this._dollyOut(this._dollyDelta.y),this._dollyStart.copy(this._dollyEnd);const h=(t.pageX+e.x)*.5,l=(t.pageY+e.y)*.5;this._updateZoomParameters(h,l)}_handleTouchMoveDollyPan(t){this.enableZoom&&this._handleTouchMoveDolly(t),this.enablePan&&this._handleTouchMovePan(t)}_handleTouchMoveDollyRotate(t){this.enableZoom&&this._handleTouchMoveDolly(t),this.enableRotate&&this._handleTouchMoveRotate(t)}_addPointer(t){this._pointers.push(t.pointerId)}_removePointer(t){delete this._pointerPositions[t.pointerId];for(let e=0;e<this._pointers.length;e++)if(this._pointers[e]==t.pointerId){this._pointers.splice(e,1);return}}_isTrackingPointer(t){for(let e=0;e<this._pointers.length;e++)if(this._pointers[e]==t.pointerId)return!0;return!1}_trackPointer(t){let e=this._pointerPositions[t.pointerId];e===void 0&&(e=new r.Vector2,this._pointerPositions[t.pointerId]=e),e.set(t.pageX,t.pageY)}_getSecondPointerPosition(t){const e=t.pointerId===this._pointers[0]?this._pointers[1]:this._pointers[0];return this._pointerPositions[e]}_customWheelEvent(t){const e=t.deltaMode,i={clientX:t.clientX,clientY:t.clientY,deltaY:t.deltaY};switch(e){case 1:i.deltaY*=16;break;case 2:i.deltaY*=100;break}return t.ctrlKey&&!this._controlActive&&(i.deltaY*=10),i}}function j(o){this.enabled!==!1&&(this._pointers.length===0&&(this.domElement.setPointerCapture(o.pointerId),this.domElement.ownerDocument.addEventListener("pointermove",this._onPointerMove),this.domElement.ownerDocument.addEventListener("pointerup",this._onPointerUp)),!this._isTrackingPointer(o)&&(this._addPointer(o),o.pointerType==="touch"?this._onTouchStart(o):this._onMouseDown(o)))}function N(o){this.enabled!==!1&&(o.pointerType==="touch"?this._onTouchMove(o):this._onMouseMove(o))}function I(o){switch(this._removePointer(o),this._pointers.length){case 0:this.domElement.releasePointerCapture(o.pointerId),this.domElement.ownerDocument.removeEventListener("pointermove",this._onPointerMove),this.domElement.ownerDocument.removeEventListener("pointerup",this._onPointerUp),this.dispatchEvent(O),this.state=c.NONE;break;case 1:const t=this._pointers[0],e=this._pointerPositions[t];this._onTouchStart({pointerId:t,pageX:e.x,pageY:e.y});break}}function R(o){let t;switch(o.button){case 0:t=this.mouseButtons.LEFT;break;case 1:t=this.mouseButtons.MIDDLE;break;case 2:t=this.mouseButtons.RIGHT;break;default:t=-1}switch(t){case r.MOUSE.DOLLY:if(this.enableZoom===!1)return;this._handleMouseDownDolly(o),this.state=c.DOLLY;break;case r.MOUSE.ROTATE:if(o.ctrlKey||o.metaKey||o.shiftKey){if(this.enablePan===!1)return;this._handleMouseDownPan(o),this.state=c.PAN}else{if(this.enableRotate===!1)return;this._handleMouseDownRotate(o),this.state=c.ROTATE}break;case r.MOUSE.PAN:if(o.ctrlKey||o.metaKey||o.shiftKey){if(this.enableRotate===!1)return;this._handleMouseDownRotate(o),this.state=c.ROTATE}else{if(this.enablePan===!1)return;this._handleMouseDownPan(o),this.state=c.PAN}break;default:this.state=c.NONE}this.state!==c.NONE&&this.dispatchEvent(M)}function U(o){switch(this.state){case c.ROTATE:if(this.enableRotate===!1)return;this._handleMouseMoveRotate(o);break;case c.DOLLY:if(this.enableZoom===!1)return;this._handleMouseMoveDolly(o);break;case c.PAN:if(this.enablePan===!1)return;this._handleMouseMovePan(o);break}}function F(o){this.enabled===!1||this.enableZoom===!1||this.state!==c.NONE||(o.preventDefault(),this.dispatchEvent(M),this._handleMouseWheel(this._customWheelEvent(o)),this.dispatchEvent(O))}function Y(o){this.enabled!==!1&&this._handleKeyDown(o)}function G(o){switch(this._trackPointer(o),this._pointers.length){case 1:switch(this.touches.ONE){case r.TOUCH.ROTATE:if(this.enableRotate===!1)return;this._handleTouchStartRotate(o),this.state=c.TOUCH_ROTATE;break;case r.TOUCH.PAN:if(this.enablePan===!1)return;this._handleTouchStartPan(o),this.state=c.TOUCH_PAN;break;default:this.state=c.NONE}break;case 2:switch(this.touches.TWO){case r.TOUCH.DOLLY_PAN:if(this.enableZoom===!1&&this.enablePan===!1)return;this._handleTouchStartDollyPan(o),this.state=c.TOUCH_DOLLY_PAN;break;case r.TOUCH.DOLLY_ROTATE:if(this.enableZoom===!1&&this.enableRotate===!1)return;this._handleTouchStartDollyRotate(o),this.state=c.TOUCH_DOLLY_ROTATE;break;default:this.state=c.NONE}break;default:this.state=c.NONE}this.state!==c.NONE&&this.dispatchEvent(M)}function V(o){switch(this._trackPointer(o),this.state){case c.TOUCH_ROTATE:if(this.enableRotate===!1)return;this._handleTouchMoveRotate(o),this.update();break;case c.TOUCH_PAN:if(this.enablePan===!1)return;this._handleTouchMovePan(o),this.update();break;case c.TOUCH_DOLLY_PAN:if(this.enableZoom===!1&&this.enablePan===!1)return;this._handleTouchMoveDollyPan(o),this.update();break;case c.TOUCH_DOLLY_ROTATE:if(this.enableZoom===!1&&this.enableRotate===!1)return;this._handleTouchMoveDollyRotate(o),this.update();break;default:this.state=c.NONE}}function z(o){this.enabled!==!1&&o.preventDefault()}function K(o){o.key==="Control"&&(this._controlActive=!0,this.domElement.getRootNode().addEventListener("keyup",this._interceptControlUp,{passive:!0,capture:!0}))}function W(o){o.key==="Control"&&(this._controlActive=!1,this.domElement.getRootNode().removeEventListener("keyup",this._interceptControlUp,{passive:!0,capture:!0}))}class C{loader;constructor(){this.loader=new S.GLTFLoader}async load(t){console.log(`[Flow] Loading avatar config from: ${t}`);try{const e=await fetch(t);if(!e.ok)throw new Error(`Failed to load config: ${e.statusText}`);const i=await e.json(),a=t.substring(0,t.lastIndexOf("/")+1)+i.modelSrc;console.log(`[Flow] Config loaded. Loading model from: ${a}`);let h,l=[];try{const d=await this.loader.loadAsync(a);h=d.scene,l=d.animations||[]}catch(d){console.warn(`[Flow] Failed to load 3D model at ${a}. Using fallback placeholder.`,d),h=this.createFallbackAvatar()}return this.applyConfig(h,i),{model:h,config:i,animations:l}}catch(e){throw console.error("[Flow] Error loading avatar:",e),e}}applyConfig(t,e){e.scale&&t.scale.setScalar(e.scale),e.initialPosition&&t.position.set(...e.initialPosition),t.traverse(i=>{i.isMesh&&(i.castShadow=!0,i.receiveShadow=!0)})}createFallbackAvatar(){const t=new n.Group,e=new n.MeshStandardMaterial({color:54015,roughness:.3,metalness:.8}),i=new n.BoxGeometry(.8,.9,.8),s=new n.Mesh(i,e);s.position.y=1.5,s.name="Head",t.add(s);const a=new n.SphereGeometry(.1),h=new n.MeshBasicMaterial({color:16777215}),l=new n.Mesh(a,h);l.position.set(-.2,1.5,.4),t.add(l);const d=new n.Mesh(a,h);d.position.set(.2,1.5,.4),t.add(d);const f=new n.CylinderGeometry(.6,.4,1.5,8),g=new n.Mesh(f,e);g.position.y=.5,g.name="Body",t.add(g);const y=new n.CapsuleGeometry(.15,1),w=new n.Mesh(y,e);w.position.set(-.9,.8,0),w.rotation.z=Math.PI/4,w.name="LeftArm",t.add(w);const P=new n.Mesh(y,e);return P.position.set(.9,.8,0),P.rotation.z=-Math.PI/4,P.name="RightArm",t.add(P),t}}class Z{loader;constructor(){this.loader=new S.GLTFLoader}async load(t){try{const e=await fetch(t);if(!e.ok)throw new Error("Failed to load stage config");const i=await e.json();let s,a=[];if(i.modelSrc){const l=t.substring(0,t.lastIndexOf("/")+1)+i.modelSrc,d=await this.loader.loadAsync(l);s=d.scene,a=d.animations||[]}else s=this.createProceduralStage();return this.applyConfig(s,i),{model:s,config:i,animations:a}}catch(e){throw console.error("[Flow] Stage load error:",e),e}}applyConfig(t,e){e.scale&&t.scale.setScalar(e.scale),e.position&&t.position.set(...e.position),e.rotation&&t.rotation.set(...e.rotation),t.traverse(i=>{i.isMesh&&(i.receiveShadow=!0,i.castShadow=!0)})}createProceduralStage(){const t=new n.Group,e=new n.CylinderGeometry(2,2.2,.2,32),i=new n.MeshStandardMaterial({color:2236962,roughness:.2,metalness:.8}),s=new n.Mesh(e,i);s.position.y=-.1,t.add(s);const a=new n.TorusGeometry(1.8,.05,16,100),h=new n.MeshBasicMaterial({color:54015}),l=new n.Mesh(a,h);l.rotation.x=-Math.PI/2,l.position.y=.01,t.add(l);const d=new n.BoxGeometry(.5,.05,.5);for(let f=0;f<4;f++){const g=new n.Mesh(d,i),y=f/4*Math.PI*2;g.position.set(Math.cos(y)*2.5,0,Math.sin(y)*2.5),g.lookAt(0,0,0),t.add(g)}return t}}class A{mixer;clips;states={};activeActions=new Map;currentState=null;defaultState="idle";constructor(t,e){this.mixer=new n.AnimationMixer(t),this.clips=e,this.mixer.addEventListener("finished",this.onFinished.bind(this))}init(t){this.defaultState=t.defaultState,this.states=t.states,Object.entries(this.states).forEach(([e,i])=>{this.findClip(i.clipName)||console.warn(`[AnimationController] Clip "${i.clipName}" for state "${e}" not found.`)}),this.play(this.defaultState)}update(t){this.mixer.update(t)}play(t,e=!1){const i=this.states[t];if(!i){console.warn(`[AnimationController] State "${t}" not defined.`);return}const s=this.findClip(i.clipName);if(!s||this.currentState===t&&!e)return;const a=this.mixer.clipAction(s);a.setLoop(i.loop?n.LoopRepeat:n.LoopOnce,i.loop?1/0:1),a.clampWhenFinished=!i.loop,a.timeScale=i.timeScale??1;const h=i.fadeDuration??.3;if(this.currentState){const l=this.activeActions.get(this.currentState);l&&l!==a?(l.fadeOut(h),a.reset(),a.fadeIn(h),a.play()):a.reset().play()}else a.reset().play();this.activeActions.set(t,a),this.currentState=t,console.log(`[Anim] Transition to: ${t} (Loop: ${i.loop})`)}onFinished(t){if(this.currentState&&this.activeActions.get(this.currentState)===t.action){const e=this.states[this.currentState],i=()=>{e&&e.next?this.play(e.next):!e.loop&&this.currentState!==this.defaultState&&this.play(this.defaultState)};e.holdDuration&&e.holdDuration>0?setTimeout(i,e.holdDuration*1e3):i()}}findClip(t){let e=this.clips.find(i=>i.name===t);return e||(e=this.clips.find(i=>i.name.toLowerCase()===t.toLowerCase())),e||(e=this.clips.find(i=>i.name.toLowerCase().includes(t.toLowerCase()))),e}}const m={IDLE:"IDLE",TRACKING:"TRACKING",HOLDING:"HOLDING"},B=5,Q=1.5,X=Number.EPSILON;class H{raycaster=new n.Raycaster;mouse=new n.Vector2;state=m.IDLE;stateTimer=0;lookAtTarget=new n.Vector3;currentLookAt=new n.Vector3;activePlane=new n.Plane(new n.Vector3(0,0,1),-2.5);planeCenter=new n.Vector3(0,1.5,2.5);lookAtProxy=new n.Object3D;_targetQuat=new n.Quaternion;_dummyVec=new n.Vector3;_offsetQuat=new n.Quaternion;_parentWorldQuat=new n.Quaternion;outputQuaternion=null;weight=0;container;camera;getHeadBone;getConfig;getModels;boundOnPointerDown;boundOnPointerMove;boundOnPointerUp;constructor(t,e,i,s,a){this.container=t,this.camera=e,this.getHeadBone=i,this.getConfig=s,this.getModels=a,this.boundOnPointerDown=this.onPointerDown.bind(this),this.boundOnPointerMove=this.onPointerMove.bind(this),this.boundOnPointerUp=this.onPointerUp.bind(this),this.container.addEventListener("pointerdown",this.boundOnPointerDown),this.container.addEventListener("pointermove",this.boundOnPointerMove),window.addEventListener("pointerup",this.boundOnPointerUp)}update(t,e){const i=this.getHeadBone(),s=this.getConfig();if(!i||!s||s.lookAt?.enabled===!1)return;const a=i.quaternion.clone();this.outputQuaternion||(this.outputQuaternion=a.clone()),this.updateState(t,s);const h=this.state===m.TRACKING||this.state===m.HOLDING;h?(this.calculateLookAtRotation(i,s,this._targetQuat),this.weight=1):(this._targetQuat.copy(a),this.weight=0);const l=s.lookAt?.damping??B,d=1-Math.exp(-l*e);this.outputQuaternion.slerp(this._targetQuat,d),i.quaternion.copy(this.outputQuaternion),h&&this.currentLookAt.lerp(this.lookAtTarget,d)}calculateLookAtRotation(t,e,i){const s=this._dummyVec;t.getWorldPosition(s),this.lookAtProxy.position.copy(s),this.lookAtProxy.lookAt(this.lookAtTarget),this.lookAtProxy.updateMatrixWorld();const a=e.lookAt?.rotationOffset||[0,0,0];this._offsetQuat.setFromEuler(new n.Euler(...a));const h=this.lookAtProxy.quaternion.multiply(this._offsetQuat),l=t.parent;if(l){l.getWorldQuaternion(this._parentWorldQuat);const d=this._parentWorldQuat.invert();i.copy(d.multiply(h))}else i.copy(h)}updateState(t,e){const i=e.lookAt?.holdDuration??2e3;switch(this.state){case m.TRACKING:this.calculateLookAtTarget();break;case m.HOLDING:t-this.stateTimer>i&&(this.state=m.IDLE,console.log("[Flow] LookAt: Holding finished, releasing to animation."));break}}onPointerDown(t){this.updateMouse(t);const e=this.getHeadBone();if(e){const i=new n.Vector3;e.getWorldPosition(i);const s=this.camera.position.clone(),a=new n.Vector3().subVectors(s,i),h=new n.Vector3;if(a.lengthSq()<1e-6){const d=this.camera;"getWorldDirection"in d&&typeof d.getWorldDirection=="function"?(d.getWorldDirection(h),h.negate()):h.set(0,0,1)}else h.copy(a).normalize();const l=h.clone().multiplyScalar(Q);this.planeCenter.copy(i).add(l),this.activePlane.setFromNormalAndCoplanarPoint(h,this.planeCenter)}this.state=m.TRACKING,this.calculateLookAtTarget(),this.outputQuaternion||this.currentLookAt.copy(this.lookAtTarget)}onPointerMove(t){this.state===m.TRACKING&&this.updateMouse(t)}onPointerUp(){this.state===m.TRACKING&&(this.state=m.HOLDING,this.stateTimer=performance.now())}updateMouse(t){const e=this.container.getBoundingClientRect();this.mouse.x=(t.clientX-e.left)/e.width*2-1,this.mouse.y=-((t.clientY-e.top)/e.height)*2+1}calculateLookAtTarget(){this.raycaster.setFromCamera(this.mouse,this.camera);const t=this.getModels().filter(i=>!!i);if(t.length>0){const i=this.raycaster.intersectObjects(t,!0);if(i.length>0){this.lookAtTarget.copy(i[0].point);return}}const e=this._dummyVec;this.activePlane.normal.lengthSq()>X&&this.raycaster.ray.intersectPlane(this.activePlane,e)&&this.lookAtTarget.copy(e)}getDebugInfo(){return{isEngaged:this.state!==m.IDLE,currentLookAt:this.currentLookAt,activePlane:this.activePlane,planeCenter:this.planeCenter,weight:this.weight}}interrupt(){this.state=m.IDLE}dispose(){this.container.removeEventListener("pointerdown",this.boundOnPointerDown),this.container.removeEventListener("pointermove",this.boundOnPointerMove),window.removeEventListener("pointerup",this.boundOnPointerUp)}}class q{container;scene;camera;renderer;controls;clock;loader;stageLoader;avatarModel=null;stageModel=null;headBone=null;animController=null;stageAnimController=null;lookAtProcessor;currentAvatarConfig=null;isDebug=!1;debugTargetMesh=null;debugPlaneMesh=null;constructor(t){const e=document.getElementById(t);if(!e)throw new Error(`Container #${t} not found`);this.container=e,this.clock=new n.Clock,this.loader=new C,this.stageLoader=new Z,this.scene=new n.Scene,this.scene.background=new n.Color(1710618),this.scene.fog=new n.Fog(1710618,10,50),this.camera=new n.PerspectiveCamera(45,window.innerWidth/window.innerHeight,.1,100),this.camera.position.set(0,1.5,5),this.renderer=new k.WebGPURenderer({antialias:!0,alpha:!0}),this.renderer.setSize(window.innerWidth,window.innerHeight),this.renderer.setPixelRatio(window.devicePixelRatio),this.container.appendChild(this.renderer.domElement),this.setupLights(),this.controls=new x(this.camera,this.renderer.domElement),this.controls.enableDamping=!0,this.controls.target.set(0,1,0),this.lookAtProcessor=new H(this.container,this.camera,()=>this.headBone,()=>this.currentAvatarConfig,()=>{const i=[];return this.avatarModel&&i.push(this.avatarModel),this.stageModel&&i.push(this.stageModel),i}),window.addEventListener("resize",this.onWindowResize.bind(this)),this.renderer.setAnimationLoop(this.animate.bind(this))}setupLights(){const t=new n.AmbientLight(16777215,.6);this.scene.add(t);const e=new n.DirectionalLight(16777215,1);e.position.set(5,10,7),e.castShadow=!0,this.scene.add(e);const i=new n.SpotLight(54015,5);i.position.set(-5,5,-5),i.lookAt(0,1,0),this.scene.add(i)}async loadAvatar(t){this.avatarModel&&this.scene.remove(this.avatarModel);const{model:e,config:i,animations:s}=await this.loader.load(t);this.avatarModel=e,this.currentAvatarConfig=i,this.scene.add(this.avatarModel);const a=i.lookAt?.headBoneName||"Head";if(this.headBone=this.avatarModel.getObjectByName(a)||null,this.headBone||this.avatarModel.traverse(h=>{!this.headBone&&h.name.toLowerCase().includes("head")&&(this.headBone=h)}),s.length>0){this.animController=new A(this.avatarModel,s);const h=i.animations||{defaultState:"idle",states:{idle:{clipName:"Idle",loop:!0},walk:{clipName:"Walking",loop:!0},wave:{clipName:"Wave",loop:!1,next:"idle"},dance:{clipName:"Dance",loop:!1,next:"idle"},bow:{clipName:"Bow",loop:!1,next:"idle"}}};this.animController.init(h)}console.log(`[Flow] Avatar "${i.name}" loaded.`)}async loadStage(t){this.stageModel&&this.scene.remove(this.stageModel);const{model:e,config:i,animations:s}=await this.stageLoader.load(t);this.stageModel=e,this.scene.add(this.stageModel),s.length>0&&i.animations&&(this.stageAnimController=new A(this.stageModel,s),this.stageAnimController.init(i.animations))}setDebug(t){this.isDebug=t,t?this.createDebugHelpers():this.removeDebugHelpers()}createDebugHelpers(){if(this.debugTargetMesh||(this.debugTargetMesh=new n.Mesh(new n.SphereGeometry(.1,8,8),new n.MeshBasicMaterial({color:16711680,wireframe:!0,depthTest:!1})),this.debugTargetMesh.renderOrder=999,this.scene.add(this.debugTargetMesh)),!this.debugPlaneMesh){const t=new n.GridHelper(5,10,65280,34816);t.material.transparent=!0,t.material.opacity=.5,t.rotateX(Math.PI/2),this.debugPlaneMesh=t,this.scene.add(this.debugPlaneMesh)}}updateDebugHelpers(){if(!this.isDebug||!this.lookAtProcessor)return;const t=this.lookAtProcessor.getDebugInfo(),e=!!(t.planeCenter&&t.activePlane);if(this.debugTargetMesh&&(this.debugTargetMesh.position.copy(t.currentLookAt),this.debugTargetMesh.visible=t.isEngaged),this.debugPlaneMesh&&(this.debugPlaneMesh.visible=e,e)){this.debugPlaneMesh.position.copy(t.planeCenter);const i=t.activePlane.normal,s=this.debugPlaneMesh.position.clone().add(i);this.debugPlaneMesh.lookAt(s),this.debugPlaneMesh.rotateX(Math.PI/2)}}removeDebugHelpers(){this.debugTargetMesh&&(this.scene.remove(this.debugTargetMesh),this.debugTargetMesh=null),this.debugPlaneMesh&&(this.scene.remove(this.debugPlaneMesh),this.debugPlaneMesh=null)}onWindowResize(){this.camera.aspect=window.innerWidth/window.innerHeight,this.camera.updateProjectionMatrix(),this.renderer.setSize(window.innerWidth,window.innerHeight)}isAutoRotate=!1;playAction(t){this.lookAtProcessor.interrupt(),this.animController&&this.animController.play(t.toLowerCase())}animate(t){const e=this.clock.getDelta();this.avatarModel&&(this.animController&&this.animController.update(e),this.lookAtProcessor.update(t,e)),this.updateDebugHelpers(),this.stageModel&&this.stageAnimController&&this.stageAnimController.update(e),this.controls.autoRotate=this.isAutoRotate,this.controls.update(),this.renderer.render(this.scene,this.camera)}}_.AnimationController=A,_.AvatarLoader=C,_.FlowEngine=q,Object.defineProperty(_,Symbol.toStringTag,{value:"Module"})}));
|
package/dist/main.d.ts
ADDED
|
File without changes
|
package/dist/types/index.d.ts
CHANGED
|
@@ -7,6 +7,19 @@ export interface AvatarConfig {
|
|
|
7
7
|
modelSrc: string;
|
|
8
8
|
scale?: number;
|
|
9
9
|
initialPosition?: [number, number, number];
|
|
10
|
+
/** Interaction: Look at target settings */
|
|
11
|
+
lookAt?: {
|
|
12
|
+
enabled?: boolean;
|
|
13
|
+
headBoneName?: string;
|
|
14
|
+
/** Euler angles offset in radians [x, y, z] to align eyes to front */
|
|
15
|
+
rotationOffset?: [number, number, number];
|
|
16
|
+
/** How fast the head turns (0-1) */
|
|
17
|
+
lerpFactor?: number;
|
|
18
|
+
/** How long to stay looking before returning (ms) */
|
|
19
|
+
holdDuration?: number;
|
|
20
|
+
/** Smoothing damping factor (typical 5-10) */
|
|
21
|
+
damping?: number;
|
|
22
|
+
};
|
|
10
23
|
/** Animation State Machine Configuration */
|
|
11
24
|
animations?: {
|
|
12
25
|
defaultState: string;
|
|
@@ -27,6 +40,13 @@ export interface AnimationStateConfig {
|
|
|
27
40
|
/** Duration to hold the last frame before transitioning (seconds) */
|
|
28
41
|
holdDuration?: number;
|
|
29
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Interface for autonomous behavior components
|
|
45
|
+
*/
|
|
46
|
+
export interface InteractionProcessor {
|
|
47
|
+
update(timeMs: number, delta: number): void;
|
|
48
|
+
dispose(): void;
|
|
49
|
+
}
|
|
30
50
|
export interface StageConfig {
|
|
31
51
|
name: string;
|
|
32
52
|
modelSrc?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@guoquan.net/flow-engine",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "A high-performance, WebGPU-based digital human engine.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/flow.umd.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"test": "vitest run",
|
|
20
20
|
"test:watch": "vitest",
|
|
21
21
|
"test:coverage": "vitest run --coverage",
|
|
22
|
-
"preview": "vite preview",
|
|
22
|
+
"preview": "vite preview --host",
|
|
23
23
|
"prepare": "npm run build"
|
|
24
24
|
},
|
|
25
25
|
"peerDependencies": {
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@codecov/vite-plugin": "^1.9.1",
|
|
30
|
+
"@playwright/test": "^1.57.0",
|
|
30
31
|
"@types/node": "^25.0.3",
|
|
31
32
|
"@types/three": "^0.182.0",
|
|
32
33
|
"@vitest/coverage-v8": "^4.0.16",
|
|
@@ -37,5 +38,9 @@
|
|
|
37
38
|
"vite": "^7.2.4",
|
|
38
39
|
"vite-plugin-dts": "^4.5.4",
|
|
39
40
|
"vitest": "^4.0.16"
|
|
41
|
+
},
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/guoquan/flow-engine.git"
|
|
40
45
|
}
|
|
41
|
-
}
|
|
46
|
+
}
|