|
@@ -23,6 +23,7 @@ const AiorzWebARLanding = () => {
|
|
|
const [cameraActive, setCameraActive] = useState(false);
|
|
const [cameraActive, setCameraActive] = useState(false);
|
|
|
const [systemStatus, setSystemStatus] = useState("SYSTEM_INIT");
|
|
const [systemStatus, setSystemStatus] = useState("SYSTEM_INIT");
|
|
|
const [gestureMode, setGestureMode] = useState("NEUTRAL");
|
|
const [gestureMode, setGestureMode] = useState("NEUTRAL");
|
|
|
|
|
+ const [latency, setLatency] = useState<number>(0);
|
|
|
|
|
|
|
|
// Refs for WebGL and MediaPipe
|
|
// Refs for WebGL and MediaPipe
|
|
|
const videoRef = useRef<HTMLVideoElement>(null);
|
|
const videoRef = useRef<HTMLVideoElement>(null);
|
|
@@ -35,6 +36,8 @@ const AiorzWebARLanding = () => {
|
|
|
const particlesRef = useRef<any>(null);
|
|
const particlesRef = useRef<any>(null);
|
|
|
const handsRef = useRef<any>(null);
|
|
const handsRef = useRef<any>(null);
|
|
|
const cameraUtilsRef = useRef<any>(null);
|
|
const cameraUtilsRef = useRef<any>(null);
|
|
|
|
|
+ const frameTimestampRef = useRef<number>(0);
|
|
|
|
|
+ const latencySamplesRef = useRef<number[]>([]); // 存储延迟样本用于计算平均值
|
|
|
|
|
|
|
|
// Physics State Refs
|
|
// Physics State Refs
|
|
|
const particlesDataRef = useRef<{
|
|
const particlesDataRef = useRef<{
|
|
@@ -114,6 +117,38 @@ const AiorzWebARLanding = () => {
|
|
|
|
|
|
|
|
// ==================== 1. Initialization ====================
|
|
// ==================== 1. Initialization ====================
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
|
|
+ // 提前预加载 MediaPipe 资源文件(在脚本加载前就开始)
|
|
|
|
|
+ const preloadMediaPipeResources = async () => {
|
|
|
|
|
+ const baseUrl = 'https://cdn.jsdelivr.net/npm/@mediapipe/hands/';
|
|
|
|
|
+ const resources = [
|
|
|
|
|
+ 'hands_solution_packed_assets.data',
|
|
|
|
|
+ 'hands_solution_simd_wasm_bin.wasm'
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ // 使用 fetch 预加载资源文件
|
|
|
|
|
+ const preloadPromises = resources.map(resource => {
|
|
|
|
|
+ return fetch(`${baseUrl}${resource}`, {
|
|
|
|
|
+ method: 'HEAD',
|
|
|
|
|
+ cache: 'force-cache'
|
|
|
|
|
+ }).catch(() => {
|
|
|
|
|
+ // 如果 HEAD 请求失败,尝试 GET 请求
|
|
|
|
|
+ return fetch(`${baseUrl}${resource}`, {
|
|
|
|
|
+ cache: 'force-cache'
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ await Promise.all(preloadPromises);
|
|
|
|
|
+ console.log('[MediaPipe] 资源文件预加载完成');
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.warn('[MediaPipe] 资源文件预加载失败:', err);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 立即开始预加载资源(不等待脚本加载)
|
|
|
|
|
+ preloadMediaPipeResources();
|
|
|
|
|
+
|
|
|
const initSystem = async () => {
|
|
const initSystem = async () => {
|
|
|
try {
|
|
try {
|
|
|
setSystemStatus("LOADING_MODULES");
|
|
setSystemStatus("LOADING_MODULES");
|
|
@@ -124,7 +159,7 @@ const AiorzWebARLanding = () => {
|
|
|
await loadScript('https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js');
|
|
await loadScript('https://cdn.jsdelivr.net/npm/@mediapipe/hands/hands.js');
|
|
|
|
|
|
|
|
initThreeJS();
|
|
initThreeJS();
|
|
|
- initMediaPipe();
|
|
|
|
|
|
|
+ await initMediaPipe();
|
|
|
|
|
|
|
|
setLoading(false);
|
|
setLoading(false);
|
|
|
setSystemStatus("READY_TO_LINK");
|
|
setSystemStatus("READY_TO_LINK");
|
|
@@ -143,6 +178,33 @@ const AiorzWebARLanding = () => {
|
|
|
};
|
|
};
|
|
|
}, []);
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
+ // ==================== 延迟平均值计算 ====================
|
|
|
|
|
+ useEffect(() => {
|
|
|
|
|
+ if (!cameraActive) {
|
|
|
|
|
+ // 摄像头未激活时,清空样本数组并重置延迟显示
|
|
|
|
|
+ latencySamplesRef.current = [];
|
|
|
|
|
+ setLatency(0);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 每秒计算一次平均值并更新延迟显示
|
|
|
|
|
+ const interval = setInterval(() => {
|
|
|
|
|
+ const samples = latencySamplesRef.current;
|
|
|
|
|
+ if (samples.length > 0) {
|
|
|
|
|
+ // 计算平均值
|
|
|
|
|
+ const sum = samples.reduce((acc, val) => acc + val, 0);
|
|
|
|
|
+ const average = Math.round(sum / samples.length);
|
|
|
|
|
+ setLatency(average);
|
|
|
|
|
+ // 清空样本数组,准备下一秒的数据收集
|
|
|
|
|
+ latencySamplesRef.current = [];
|
|
|
|
|
+ }
|
|
|
|
|
+ }, 1000); // 每秒更新一次
|
|
|
|
|
+
|
|
|
|
|
+ return () => {
|
|
|
|
|
+ clearInterval(interval);
|
|
|
|
|
+ };
|
|
|
|
|
+ }, [cameraActive]);
|
|
|
|
|
+
|
|
|
// ==================== 2. Three.js Setup ====================
|
|
// ==================== 2. Three.js Setup ====================
|
|
|
const initThreeJS = () => {
|
|
const initThreeJS = () => {
|
|
|
const THREE = (window as any).THREE;
|
|
const THREE = (window as any).THREE;
|
|
@@ -655,7 +717,9 @@ const AiorzWebARLanding = () => {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- setGestureMode(key.toUpperCase());
|
|
|
|
|
|
|
+ // 当手势是 'text' 时,显示 'AI ORZ' 而不是 'TEXT'
|
|
|
|
|
+ const displayText = key === 'text' ? 'AI ORZ' : key.toUpperCase();
|
|
|
|
|
+ setGestureMode(displayText);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// ==================== 4. Physics Loop ====================
|
|
// ==================== 4. Physics Loop ====================
|
|
@@ -899,8 +963,9 @@ const AiorzWebARLanding = () => {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// ==================== 5. MediaPipe Logic ====================
|
|
// ==================== 5. MediaPipe Logic ====================
|
|
|
- const initMediaPipe = () => {
|
|
|
|
|
|
|
+ const initMediaPipe = async () => {
|
|
|
const Hands = (window as any).Hands;
|
|
const Hands = (window as any).Hands;
|
|
|
|
|
+
|
|
|
const hands = new Hands({locateFile: (file: string) => {
|
|
const hands = new Hands({locateFile: (file: string) => {
|
|
|
return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`;
|
|
return `https://cdn.jsdelivr.net/npm/@mediapipe/hands/${file}`;
|
|
|
}});
|
|
}});
|
|
@@ -914,10 +979,48 @@ const AiorzWebARLanding = () => {
|
|
|
|
|
|
|
|
hands.onResults(onResults);
|
|
hands.onResults(onResults);
|
|
|
handsRef.current = hands;
|
|
handsRef.current = hands;
|
|
|
|
|
+
|
|
|
|
|
+ // 通过发送测试图像来强制 MediaPipe 初始化并加载资源
|
|
|
|
|
+ // 由于资源文件已经在初始化阶段预加载,这里主要是触发 MediaPipe 的初始化
|
|
|
|
|
+ try {
|
|
|
|
|
+ const preloadCanvas = document.createElement('canvas');
|
|
|
|
|
+ preloadCanvas.width = 640;
|
|
|
|
|
+ preloadCanvas.height = 480;
|
|
|
|
|
+ const ctx = preloadCanvas.getContext('2d');
|
|
|
|
|
+ if (ctx) {
|
|
|
|
|
+ ctx.fillStyle = 'black';
|
|
|
|
|
+ ctx.fillRect(0, 0, 640, 480);
|
|
|
|
|
+
|
|
|
|
|
+ // 发送一个测试图像来触发 MediaPipe 初始化
|
|
|
|
|
+ // 这会强制 MediaPipe 加载并初始化所有必要的资源
|
|
|
|
|
+ // 由于资源文件已经预加载,这里应该会更快完成
|
|
|
|
|
+ await hands.send({ image: preloadCanvas as any });
|
|
|
|
|
+ console.log('[MediaPipe] MediaPipe 实例初始化完成,资源已就绪');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (err) {
|
|
|
|
|
+ console.warn('[MediaPipe] MediaPipe 初始化失败,将在首次使用时加载:', err);
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const startCameraSystem = async () => {
|
|
const startCameraSystem = async () => {
|
|
|
if (cameraActive) return;
|
|
if (cameraActive) return;
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否支持 getUserMedia
|
|
|
|
|
+ if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
|
|
|
|
+ const isLocalhost = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1';
|
|
|
|
|
+ const isSecure = window.location.protocol === 'https:';
|
|
|
|
|
+
|
|
|
|
|
+ if (!isLocalhost && !isSecure) {
|
|
|
|
|
+ setSystemStatus("HTTPS_REQUIRED");
|
|
|
|
|
+ alert('Camera access requires HTTPS connection. Please use https:// to access this page, or configure an SSL certificate.');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ setSystemStatus("CAMERA_NOT_SUPPORTED");
|
|
|
|
|
+ alert('Your browser does not support camera access functionality.');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
setSystemStatus("INITIALIZING_VISION_CORE");
|
|
setSystemStatus("INITIALIZING_VISION_CORE");
|
|
|
|
|
|
|
|
const Camera = (window as any).Camera;
|
|
const Camera = (window as any).Camera;
|
|
@@ -925,6 +1028,8 @@ const AiorzWebARLanding = () => {
|
|
|
onFrame: async () => {
|
|
onFrame: async () => {
|
|
|
if (handsRef.current && videoRef.current && videoRef.current.readyState >= 2) {
|
|
if (handsRef.current && videoRef.current && videoRef.current.readyState >= 2) {
|
|
|
try {
|
|
try {
|
|
|
|
|
+ // 记录发送帧的时间戳
|
|
|
|
|
+ frameTimestampRef.current = performance.now();
|
|
|
await handsRef.current.send({image: videoRef.current});
|
|
await handsRef.current.send({image: videoRef.current});
|
|
|
} catch (e) {}
|
|
} catch (e) {}
|
|
|
}
|
|
}
|
|
@@ -945,6 +1050,15 @@ const AiorzWebARLanding = () => {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
const onResults = (results: any) => {
|
|
const onResults = (results: any) => {
|
|
|
|
|
+ // 计算延迟:从发送帧到收到结果的耗时
|
|
|
|
|
+ if (frameTimestampRef.current > 0) {
|
|
|
|
|
+ const currentTime = performance.now();
|
|
|
|
|
+ const frameLatency = currentTime - frameTimestampRef.current;
|
|
|
|
|
+ // 将延迟值添加到样本数组中,而不是直接更新 state
|
|
|
|
|
+ latencySamplesRef.current.push(Math.round(frameLatency));
|
|
|
|
|
+ frameTimestampRef.current = 0; // 重置时间戳
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
|
|
if (results.multiHandLandmarks && results.multiHandLandmarks.length > 0) {
|
|
|
const lm = results.multiHandLandmarks[0];
|
|
const lm = results.multiHandLandmarks[0];
|
|
|
handStateRef.current.landmarks = lm;
|
|
handStateRef.current.landmarks = lm;
|
|
@@ -1004,7 +1118,7 @@ const AiorzWebARLanding = () => {
|
|
|
<div className="w-8 h-8 bg-emerald-500/10 border border-emerald-500/50 rounded flex items-center justify-center group-hover:bg-emerald-500/20 transition-all duration-300">
|
|
<div className="w-8 h-8 bg-emerald-500/10 border border-emerald-500/50 rounded flex items-center justify-center group-hover:bg-emerald-500/20 transition-all duration-300">
|
|
|
<Cpu size={18} className="text-emerald-400" />
|
|
<Cpu size={18} className="text-emerald-400" />
|
|
|
</div>
|
|
</div>
|
|
|
- <span className="text-xl font-bold tracking-wider text-white group-hover:text-emerald-400 transition-colors">AIORZ</span>
|
|
|
|
|
|
|
+ <span className="text-xl font-bold tracking-wider text-white group-hover:text-emerald-400 transition-colors text-glow">AIORZ</span>
|
|
|
</div>
|
|
</div>
|
|
|
<div className="hidden md:flex gap-6 text-sm items-center">
|
|
<div className="hidden md:flex gap-6 text-sm items-center">
|
|
|
<div className="flex items-center gap-2 text-emerald-500/80">
|
|
<div className="flex items-center gap-2 text-emerald-500/80">
|
|
@@ -1025,21 +1139,21 @@ const AiorzWebARLanding = () => {
|
|
|
<main className="relative z-20 max-w-7xl mx-auto px-6 pt-16 h-[calc(100vh-100px)]">
|
|
<main className="relative z-20 max-w-7xl mx-auto px-6 pt-16 h-[calc(100vh-100px)]">
|
|
|
{/* 左侧主要内容区域 */}
|
|
{/* 左侧主要内容区域 */}
|
|
|
<div className="max-w-2xl space-y-8">
|
|
<div className="max-w-2xl space-y-8">
|
|
|
- <div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-emerald-900/30 border border-emerald-500/30 text-emerald-400 text-xs font-bold uppercase tracking-widest">
|
|
|
|
|
|
|
+ <div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-emerald-900/30 border border-emerald-500/30 text-emerald-400 text-xs font-bold uppercase tracking-widest text-pulse-glow">
|
|
|
<span className={`w-2 h-2 rounded-full ${cameraActive ? 'bg-emerald-400 animate-ping' : 'bg-gray-500'}`}></span>
|
|
<span className={`w-2 h-2 rounded-full ${cameraActive ? 'bg-emerald-400 animate-ping' : 'bg-gray-500'}`}></span>
|
|
|
{cameraActive ? 'Neural Link Active' : 'System Standby'}
|
|
{cameraActive ? 'Neural Link Active' : 'System Standby'}
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<h1 className="text-5xl md:text-7xl font-black text-white leading-tight">
|
|
<h1 className="text-5xl md:text-7xl font-black text-white leading-tight">
|
|
|
- Don't Kneel <br />
|
|
|
|
|
- <span className="text-transparent bg-clip-text bg-gradient-to-r from-emerald-400 to-cyan-500">
|
|
|
|
|
|
|
+ <span className="text-glow">Don't Kneel</span> <br />
|
|
|
|
|
+ <span className="gradient-animated">
|
|
|
To Complexity.
|
|
To Complexity.
|
|
|
</span>
|
|
</span>
|
|
|
</h1>
|
|
</h1>
|
|
|
|
|
|
|
|
<p className="text-lg text-gray-400 max-w-lg leading-relaxed border-l-2 border-emerald-500/50 pl-6">
|
|
<p className="text-lg text-gray-400 max-w-lg leading-relaxed border-l-2 border-emerald-500/50 pl-6">
|
|
|
- The next-gen AI agent for cross-border commerce. <br/>
|
|
|
|
|
- Unleash the power of <span className="text-emerald-400 font-bold">Spatial Computing</span>.
|
|
|
|
|
|
|
+ <span className="text-pulse-glow">We are on the verge of creating a great AI era.</span> <br/>
|
|
|
|
|
+ Unleash the power of <span className="text-shimmer font-bold">Spatial Computing</span>.
|
|
|
</p>
|
|
</p>
|
|
|
|
|
|
|
|
<div className="pt-4">
|
|
<div className="pt-4">
|
|
@@ -1058,7 +1172,7 @@ const AiorzWebARLanding = () => {
|
|
|
<div className="flex gap-4">
|
|
<div className="flex gap-4">
|
|
|
<div className="p-4 bg-emerald-950/50 border border-emerald-500/30 rounded-lg backdrop-blur-sm">
|
|
<div className="p-4 bg-emerald-950/50 border border-emerald-500/30 rounded-lg backdrop-blur-sm">
|
|
|
<div className="text-xs text-gray-400 mb-1">CURRENT GESTURE</div>
|
|
<div className="text-xs text-gray-400 mb-1">CURRENT GESTURE</div>
|
|
|
- <div className="text-xl font-bold text-white tracking-widest">{gestureMode}</div>
|
|
|
|
|
|
|
+ <div className="text-xl font-bold text-white tracking-widest text-glow">{gestureMode}</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
)}
|
|
)}
|
|
@@ -1092,7 +1206,7 @@ const AiorzWebARLanding = () => {
|
|
|
</div>
|
|
</div>
|
|
|
<div className="flex justify-between items-center">
|
|
<div className="flex justify-between items-center">
|
|
|
<span className="text-gray-500">Latency:</span>
|
|
<span className="text-gray-500">Latency:</span>
|
|
|
- <span className="text-cyan-400">16ms</span>
|
|
|
|
|
|
|
+ <span className="text-cyan-400">{latency}ms</span>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
@@ -1200,7 +1314,7 @@ const AiorzWebARLanding = () => {
|
|
|
</div>
|
|
</div>
|
|
|
<div className="mt-3 pt-2 border-t border-gray-700/50 text-[10px] text-gray-500 leading-tight">
|
|
<div className="mt-3 pt-2 border-t border-gray-700/50 text-[10px] text-gray-500 leading-tight">
|
|
|
> tracking: {cameraActive ? <span className="text-green-500">ON</span> : <span className="text-red-500">OFF</span>} <br/>
|
|
> tracking: {cameraActive ? <span className="text-green-500">ON</span> : <span className="text-red-500">OFF</span>} <br/>
|
|
|
- > latency: 16ms
|
|
|
|
|
|
|
+ > latency: {latency}ms
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|