@aether-stack-dev/client-sdk 1.3.1 → 1.3.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.
@@ -33,6 +33,7 @@ export interface VRMAvatarProps {
33
33
  environmentBlur?: number;
34
34
  environmentZoom?: number;
35
35
  onEnvironmentLoad?: () => void;
36
+ onEnvironmentProgress?: (loaded: number, total: number) => void;
36
37
  features?: VRMAvatarFeatures;
37
38
  orbitAngle?: number;
38
39
  orbitElevation?: number;
@@ -41,5 +42,5 @@ export interface VRMAvatarProps {
41
42
  cursorFollow?: boolean;
42
43
  preserveDrawingBuffer?: boolean;
43
44
  }
44
- export declare function VRMAvatar({ blendshapes, width, height, modelUrl, backgroundColor, blendshapeMap, expressionOverrides, onModelLoad, maxModelSize, cameraPosition, cameraTarget, cameraFov, lightIntensity, idleAnimationUrl, animationSpeed, animationWeight, animationCrossfade, postProcessing, environmentUrl, environmentIntensity, environmentBlur, environmentZoom, onEnvironmentLoad, features, orbitAngle, orbitElevation, avatarRotation, onOrbitChange, cursorFollow, preserveDrawingBuffer, }: VRMAvatarProps): import("react/jsx-runtime").JSX.Element;
45
+ export declare function VRMAvatar({ blendshapes, width, height, modelUrl, backgroundColor, blendshapeMap, expressionOverrides, onModelLoad, maxModelSize, cameraPosition, cameraTarget, cameraFov, lightIntensity, idleAnimationUrl, animationSpeed, animationWeight, animationCrossfade, postProcessing, environmentUrl, environmentIntensity, environmentBlur, environmentZoom, onEnvironmentLoad, onEnvironmentProgress, features, orbitAngle, orbitElevation, avatarRotation, onOrbitChange, cursorFollow, preserveDrawingBuffer, }: VRMAvatarProps): import("react/jsx-runtime").JSX.Element;
45
46
  export default VRMAvatar;
@@ -3,4 +3,4 @@ export interface EnvironmentResult {
3
3
  envMap: THREE.Texture;
4
4
  dispose: () => void;
5
5
  }
6
- export declare function loadEnvironment(renderer: THREE.WebGLRenderer, url: string): Promise<EnvironmentResult>;
6
+ export declare function loadEnvironment(renderer: THREE.WebGLRenderer, url: string, onProgress?: (loaded: number, total: number) => void): Promise<EnvironmentResult>;
package/dist/react.esm.js CHANGED
@@ -943,7 +943,7 @@ function getKTX2Loader(renderer) {
943
943
  }
944
944
  return ktx2Loader;
945
945
  }
946
- function loadEnvironment(renderer, url) {
946
+ function loadEnvironment(renderer, url, onProgress) {
947
947
  const cleanUrl = url.split('?')[0].split('#')[0].toLowerCase();
948
948
  // For blob URLs, the filename is passed as a hash fragment (e.g. blob:...#file.ktx2)
949
949
  const hash = (url.split('#')[1] || '').toLowerCase();
@@ -985,22 +985,25 @@ function loadEnvironment(renderer, url) {
985
985
  rt.dispose();
986
986
  pmrem.dispose();
987
987
  resolve({ envMap, dispose: () => envMap.dispose() });
988
- }, undefined, reject);
988
+ }, (e) => { if (onProgress && e.lengthComputable)
989
+ onProgress(e.loaded, e.total); }, reject);
989
990
  });
990
991
  }
991
992
  return new Promise((resolve, reject) => {
992
993
  const onLoad = (texture) => resolve(finalize(texture));
994
+ const handleProgress = (e) => { if (onProgress && e.lengthComputable)
995
+ onProgress(e.loaded, e.total); };
993
996
  if (cleanUrl.endsWith('.exr') || hashExt === '.exr') {
994
- new EXRLoader().load(url, onLoad, undefined, reject);
997
+ new EXRLoader().load(url, onLoad, handleProgress, reject);
995
998
  }
996
999
  else {
997
- new HDRLoader().load(url, onLoad, undefined, reject);
1000
+ new HDRLoader().load(url, onLoad, handleProgress, reject);
998
1001
  }
999
1002
  });
1000
1003
  }
1001
1004
 
1002
1005
  const DEFAULT_MAX_MODEL_SIZE = 30 * 1024 * 1024; // 30MB
1003
- function VRMAvatar({ blendshapes, width = 400, height = 400, modelUrl = 'https://xwqtyabmavpacejuypyp.supabase.co/storage/v1/object/public/vrm-templates/5705015963733407866.vrm', backgroundColor = 0xffffff, blendshapeMap, expressionOverrides, onModelLoad, maxModelSize = DEFAULT_MAX_MODEL_SIZE, cameraPosition, cameraTarget, cameraFov, lightIntensity, idleAnimationUrl, animationSpeed = 1, animationWeight = 1, animationCrossfade = 0.5, postProcessing, environmentUrl, environmentIntensity, environmentBlur, environmentZoom, onEnvironmentLoad, features, orbitAngle, orbitElevation, avatarRotation, onOrbitChange, cursorFollow = true, preserveDrawingBuffer = false, }) {
1006
+ function VRMAvatar({ blendshapes, width = 400, height = 400, modelUrl = 'https://xwqtyabmavpacejuypyp.supabase.co/storage/v1/object/public/vrm-templates/5705015963733407866.vrm', backgroundColor = 0xffffff, blendshapeMap, expressionOverrides, onModelLoad, maxModelSize = DEFAULT_MAX_MODEL_SIZE, cameraPosition, cameraTarget, cameraFov, lightIntensity, idleAnimationUrl, animationSpeed = 1, animationWeight = 1, animationCrossfade = 0.5, postProcessing, environmentUrl, environmentIntensity, environmentBlur, environmentZoom, onEnvironmentLoad, onEnvironmentProgress, features, orbitAngle, orbitElevation, avatarRotation, onOrbitChange, cursorFollow = true, preserveDrawingBuffer = false, }) {
1004
1007
  const containerRef = useRef(null);
1005
1008
  const rendererRef = useRef(null);
1006
1009
  const sceneRef = useRef(null);
@@ -1023,6 +1026,8 @@ function VRMAvatar({ blendshapes, width = 400, height = 400, modelUrl = 'https:/
1023
1026
  envZoomRef.current = environmentZoom;
1024
1027
  const onEnvLoadRef = useRef(onEnvironmentLoad);
1025
1028
  onEnvLoadRef.current = onEnvironmentLoad;
1029
+ const onEnvProgressRef = useRef(onEnvironmentProgress);
1030
+ onEnvProgressRef.current = onEnvironmentProgress;
1026
1031
  const [loading, setLoading] = useState(true);
1027
1032
  const [error, setError] = useState(null);
1028
1033
  const animationFrameRef = useRef(0);
@@ -1406,7 +1411,16 @@ function VRMAvatar({ blendshapes, width = 400, height = 400, modelUrl = 'https:/
1406
1411
  container.removeChild(renderer.domElement);
1407
1412
  }
1408
1413
  };
1409
- }, [width, height, modelUrl, maxModelSize, preserveDrawingBuffer]);
1414
+ }, [modelUrl, maxModelSize, preserveDrawingBuffer]);
1415
+ // Resize renderer + camera without rebuilding scene
1416
+ useEffect(() => {
1417
+ const renderer = rendererRef.current, camera = cameraRef.current;
1418
+ if (!renderer || !camera)
1419
+ return;
1420
+ renderer.setSize(width, height);
1421
+ camera.aspect = width / height;
1422
+ camera.updateProjectionMatrix();
1423
+ }, [width, height]);
1410
1424
  useEffect(() => {
1411
1425
  composerRef.current?.dispose();
1412
1426
  composerRef.current = null;
@@ -1516,7 +1530,7 @@ function VRMAvatar({ blendshapes, width = 400, height = 400, modelUrl = 'https:/
1516
1530
  }
1517
1531
  let cancelled = false;
1518
1532
  const prev = envMapRef.current;
1519
- loadEnvironment(renderer, environmentUrl).then(r => {
1533
+ loadEnvironment(renderer, environmentUrl, (loaded, total) => onEnvProgressRef.current?.(loaded, total)).then(r => {
1520
1534
  if (cancelled) {
1521
1535
  r.dispose();
1522
1536
  return;
@@ -1648,6 +1662,32 @@ function VRMAvatar({ blendshapes, width = 400, height = 400, modelUrl = 'https:/
1648
1662
  }
1649
1663
  onOrbitChangeRef.current?.(o.targetAngle, o.targetElevation);
1650
1664
  };
1665
+ const onTouchStart = (e) => {
1666
+ if (e.touches.length !== 1)
1667
+ return;
1668
+ e.preventDefault();
1669
+ const t = e.touches[0];
1670
+ orbitRef.current.dragging = true;
1671
+ orbitRef.current.lastX = t.clientX;
1672
+ orbitRef.current.lastY = t.clientY;
1673
+ };
1674
+ const onTouchMove = (e) => {
1675
+ if (!orbitRef.current.dragging || e.touches.length !== 1)
1676
+ return;
1677
+ e.preventDefault();
1678
+ const t = e.touches[0];
1679
+ const dx = t.clientX - orbitRef.current.lastX;
1680
+ const dy = t.clientY - orbitRef.current.lastY;
1681
+ orbitRef.current.lastX = t.clientX;
1682
+ orbitRef.current.lastY = t.clientY;
1683
+ orbitRef.current.angle -= dx * 0.005;
1684
+ orbitRef.current.targetAngle = orbitRef.current.angle;
1685
+ orbitRef.current.elevation = Math.max(-Math.PI / 3, Math.min(Math.PI / 3, orbitRef.current.elevation + dy * 0.005));
1686
+ orbitRef.current.targetElevation = orbitRef.current.elevation;
1687
+ applyCameraOrbit();
1688
+ onOrbitChangeRef.current?.(orbitRef.current.angle, orbitRef.current.elevation);
1689
+ };
1690
+ const onTouchEnd = () => { orbitRef.current.dragging = false; };
1651
1691
  const onCursorMove = (e) => {
1652
1692
  const rect = container.getBoundingClientRect();
1653
1693
  cursorRef.current.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
@@ -1655,8 +1695,12 @@ function VRMAvatar({ blendshapes, width = 400, height = 400, modelUrl = 'https:/
1655
1695
  cursorRef.current.active = true;
1656
1696
  };
1657
1697
  const onCursorLeave = () => { cursorRef.current.active = false; };
1698
+ container.style.touchAction = 'none';
1658
1699
  container.addEventListener('wheel', onWheel, { passive: false });
1659
1700
  container.addEventListener('mousedown', onMouseDown);
1701
+ container.addEventListener('touchstart', onTouchStart, { passive: false });
1702
+ container.addEventListener('touchmove', onTouchMove, { passive: false });
1703
+ container.addEventListener('touchend', onTouchEnd);
1660
1704
  container.addEventListener('mousemove', onCursorMove);
1661
1705
  container.addEventListener('mouseleave', onCursorLeave);
1662
1706
  window.addEventListener('mousemove', onMouseMove);
@@ -1665,6 +1709,9 @@ function VRMAvatar({ blendshapes, width = 400, height = 400, modelUrl = 'https:/
1665
1709
  return () => {
1666
1710
  container.removeEventListener('wheel', onWheel);
1667
1711
  container.removeEventListener('mousedown', onMouseDown);
1712
+ container.removeEventListener('touchstart', onTouchStart);
1713
+ container.removeEventListener('touchmove', onTouchMove);
1714
+ container.removeEventListener('touchend', onTouchEnd);
1668
1715
  container.removeEventListener('mousemove', onCursorMove);
1669
1716
  container.removeEventListener('mouseleave', onCursorLeave);
1670
1717
  window.removeEventListener('mousemove', onMouseMove);
package/dist/react.js CHANGED
@@ -964,7 +964,7 @@ function getKTX2Loader(renderer) {
964
964
  }
965
965
  return ktx2Loader;
966
966
  }
967
- function loadEnvironment(renderer, url) {
967
+ function loadEnvironment(renderer, url, onProgress) {
968
968
  const cleanUrl = url.split('?')[0].split('#')[0].toLowerCase();
969
969
  // For blob URLs, the filename is passed as a hash fragment (e.g. blob:...#file.ktx2)
970
970
  const hash = (url.split('#')[1] || '').toLowerCase();
@@ -1006,22 +1006,25 @@ function loadEnvironment(renderer, url) {
1006
1006
  rt.dispose();
1007
1007
  pmrem.dispose();
1008
1008
  resolve({ envMap, dispose: () => envMap.dispose() });
1009
- }, undefined, reject);
1009
+ }, (e) => { if (onProgress && e.lengthComputable)
1010
+ onProgress(e.loaded, e.total); }, reject);
1010
1011
  });
1011
1012
  }
1012
1013
  return new Promise((resolve, reject) => {
1013
1014
  const onLoad = (texture) => resolve(finalize(texture));
1015
+ const handleProgress = (e) => { if (onProgress && e.lengthComputable)
1016
+ onProgress(e.loaded, e.total); };
1014
1017
  if (cleanUrl.endsWith('.exr') || hashExt === '.exr') {
1015
- new EXRLoader_js.EXRLoader().load(url, onLoad, undefined, reject);
1018
+ new EXRLoader_js.EXRLoader().load(url, onLoad, handleProgress, reject);
1016
1019
  }
1017
1020
  else {
1018
- new HDRLoader_js.HDRLoader().load(url, onLoad, undefined, reject);
1021
+ new HDRLoader_js.HDRLoader().load(url, onLoad, handleProgress, reject);
1019
1022
  }
1020
1023
  });
1021
1024
  }
1022
1025
 
1023
1026
  const DEFAULT_MAX_MODEL_SIZE = 30 * 1024 * 1024; // 30MB
1024
- function VRMAvatar({ blendshapes, width = 400, height = 400, modelUrl = 'https://xwqtyabmavpacejuypyp.supabase.co/storage/v1/object/public/vrm-templates/5705015963733407866.vrm', backgroundColor = 0xffffff, blendshapeMap, expressionOverrides, onModelLoad, maxModelSize = DEFAULT_MAX_MODEL_SIZE, cameraPosition, cameraTarget, cameraFov, lightIntensity, idleAnimationUrl, animationSpeed = 1, animationWeight = 1, animationCrossfade = 0.5, postProcessing, environmentUrl, environmentIntensity, environmentBlur, environmentZoom, onEnvironmentLoad, features, orbitAngle, orbitElevation, avatarRotation, onOrbitChange, cursorFollow = true, preserveDrawingBuffer = false, }) {
1027
+ function VRMAvatar({ blendshapes, width = 400, height = 400, modelUrl = 'https://xwqtyabmavpacejuypyp.supabase.co/storage/v1/object/public/vrm-templates/5705015963733407866.vrm', backgroundColor = 0xffffff, blendshapeMap, expressionOverrides, onModelLoad, maxModelSize = DEFAULT_MAX_MODEL_SIZE, cameraPosition, cameraTarget, cameraFov, lightIntensity, idleAnimationUrl, animationSpeed = 1, animationWeight = 1, animationCrossfade = 0.5, postProcessing, environmentUrl, environmentIntensity, environmentBlur, environmentZoom, onEnvironmentLoad, onEnvironmentProgress, features, orbitAngle, orbitElevation, avatarRotation, onOrbitChange, cursorFollow = true, preserveDrawingBuffer = false, }) {
1025
1028
  const containerRef = react.useRef(null);
1026
1029
  const rendererRef = react.useRef(null);
1027
1030
  const sceneRef = react.useRef(null);
@@ -1044,6 +1047,8 @@ function VRMAvatar({ blendshapes, width = 400, height = 400, modelUrl = 'https:/
1044
1047
  envZoomRef.current = environmentZoom;
1045
1048
  const onEnvLoadRef = react.useRef(onEnvironmentLoad);
1046
1049
  onEnvLoadRef.current = onEnvironmentLoad;
1050
+ const onEnvProgressRef = react.useRef(onEnvironmentProgress);
1051
+ onEnvProgressRef.current = onEnvironmentProgress;
1047
1052
  const [loading, setLoading] = react.useState(true);
1048
1053
  const [error, setError] = react.useState(null);
1049
1054
  const animationFrameRef = react.useRef(0);
@@ -1427,7 +1432,16 @@ function VRMAvatar({ blendshapes, width = 400, height = 400, modelUrl = 'https:/
1427
1432
  container.removeChild(renderer.domElement);
1428
1433
  }
1429
1434
  };
1430
- }, [width, height, modelUrl, maxModelSize, preserveDrawingBuffer]);
1435
+ }, [modelUrl, maxModelSize, preserveDrawingBuffer]);
1436
+ // Resize renderer + camera without rebuilding scene
1437
+ react.useEffect(() => {
1438
+ const renderer = rendererRef.current, camera = cameraRef.current;
1439
+ if (!renderer || !camera)
1440
+ return;
1441
+ renderer.setSize(width, height);
1442
+ camera.aspect = width / height;
1443
+ camera.updateProjectionMatrix();
1444
+ }, [width, height]);
1431
1445
  react.useEffect(() => {
1432
1446
  composerRef.current?.dispose();
1433
1447
  composerRef.current = null;
@@ -1537,7 +1551,7 @@ function VRMAvatar({ blendshapes, width = 400, height = 400, modelUrl = 'https:/
1537
1551
  }
1538
1552
  let cancelled = false;
1539
1553
  const prev = envMapRef.current;
1540
- loadEnvironment(renderer, environmentUrl).then(r => {
1554
+ loadEnvironment(renderer, environmentUrl, (loaded, total) => onEnvProgressRef.current?.(loaded, total)).then(r => {
1541
1555
  if (cancelled) {
1542
1556
  r.dispose();
1543
1557
  return;
@@ -1669,6 +1683,32 @@ function VRMAvatar({ blendshapes, width = 400, height = 400, modelUrl = 'https:/
1669
1683
  }
1670
1684
  onOrbitChangeRef.current?.(o.targetAngle, o.targetElevation);
1671
1685
  };
1686
+ const onTouchStart = (e) => {
1687
+ if (e.touches.length !== 1)
1688
+ return;
1689
+ e.preventDefault();
1690
+ const t = e.touches[0];
1691
+ orbitRef.current.dragging = true;
1692
+ orbitRef.current.lastX = t.clientX;
1693
+ orbitRef.current.lastY = t.clientY;
1694
+ };
1695
+ const onTouchMove = (e) => {
1696
+ if (!orbitRef.current.dragging || e.touches.length !== 1)
1697
+ return;
1698
+ e.preventDefault();
1699
+ const t = e.touches[0];
1700
+ const dx = t.clientX - orbitRef.current.lastX;
1701
+ const dy = t.clientY - orbitRef.current.lastY;
1702
+ orbitRef.current.lastX = t.clientX;
1703
+ orbitRef.current.lastY = t.clientY;
1704
+ orbitRef.current.angle -= dx * 0.005;
1705
+ orbitRef.current.targetAngle = orbitRef.current.angle;
1706
+ orbitRef.current.elevation = Math.max(-Math.PI / 3, Math.min(Math.PI / 3, orbitRef.current.elevation + dy * 0.005));
1707
+ orbitRef.current.targetElevation = orbitRef.current.elevation;
1708
+ applyCameraOrbit();
1709
+ onOrbitChangeRef.current?.(orbitRef.current.angle, orbitRef.current.elevation);
1710
+ };
1711
+ const onTouchEnd = () => { orbitRef.current.dragging = false; };
1672
1712
  const onCursorMove = (e) => {
1673
1713
  const rect = container.getBoundingClientRect();
1674
1714
  cursorRef.current.x = ((e.clientX - rect.left) / rect.width) * 2 - 1;
@@ -1676,8 +1716,12 @@ function VRMAvatar({ blendshapes, width = 400, height = 400, modelUrl = 'https:/
1676
1716
  cursorRef.current.active = true;
1677
1717
  };
1678
1718
  const onCursorLeave = () => { cursorRef.current.active = false; };
1719
+ container.style.touchAction = 'none';
1679
1720
  container.addEventListener('wheel', onWheel, { passive: false });
1680
1721
  container.addEventListener('mousedown', onMouseDown);
1722
+ container.addEventListener('touchstart', onTouchStart, { passive: false });
1723
+ container.addEventListener('touchmove', onTouchMove, { passive: false });
1724
+ container.addEventListener('touchend', onTouchEnd);
1681
1725
  container.addEventListener('mousemove', onCursorMove);
1682
1726
  container.addEventListener('mouseleave', onCursorLeave);
1683
1727
  window.addEventListener('mousemove', onMouseMove);
@@ -1686,6 +1730,9 @@ function VRMAvatar({ blendshapes, width = 400, height = 400, modelUrl = 'https:/
1686
1730
  return () => {
1687
1731
  container.removeEventListener('wheel', onWheel);
1688
1732
  container.removeEventListener('mousedown', onMouseDown);
1733
+ container.removeEventListener('touchstart', onTouchStart);
1734
+ container.removeEventListener('touchmove', onTouchMove);
1735
+ container.removeEventListener('touchend', onTouchEnd);
1689
1736
  container.removeEventListener('mousemove', onCursorMove);
1690
1737
  container.removeEventListener('mouseleave', onCursorLeave);
1691
1738
  window.removeEventListener('mousemove', onMouseMove);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aether-stack-dev/client-sdk",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "type": "module",
5
5
  "description": "JavaScript/TypeScript SDK for AStack video-to-video AI conversations",
6
6
  "main": "dist/index.js",