forked from HQU-gxy/camera-extrinsic-play
Compare commits
1 Commits
main
...
feat/boxin
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f959b129d |
16
protocol.py
Normal file
16
protocol.py
Normal file
@ -0,0 +1,16 @@
|
||||
from typing import Protocol, TypeVar, Generic
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
class Adder(Generic[T], Protocol):
|
||||
def add(self, a: T, b: T) -> T: ...
|
||||
|
||||
|
||||
class AdderImpl(Adder[int]):
|
||||
acc: int
|
||||
|
||||
def add(self, a: int, b: int):
|
||||
self.acc = a + b
|
||||
return self.acc
|
||||
|
||||
272
src/App.tsx
272
src/App.tsx
@ -1,14 +1,17 @@
|
||||
import { Grid, useBVH, useGLTF, CameraControls, AccumulativeShadows, OrbitControls, Stats } from '@react-three/drei'
|
||||
import { Camera, Canvas, useFrame, useThree, useLoader, RenderCallback, RootState } from '@react-three/fiber'
|
||||
import { Text as DreiText, TextProps as DreiTextProps } from '@react-three/drei';
|
||||
import * as THREE from 'three'
|
||||
import { FontLoader } from 'three/addons/loaders/FontLoader.js'
|
||||
import { TextGeometry } from 'three/addons/geometries/TextGeometry.js'
|
||||
import HelvetikerRegular from "three/examples/fonts/helvetiker_regular.typeface.json"
|
||||
import { useEffect, useRef, useState, JSX } from 'react'
|
||||
import { useEffect, useRef, useState, JSX, forwardRef } from 'react'
|
||||
// import POSE_3D_ from "./assets/result_ae_01_ae_08.json"
|
||||
import POSE_3D_ from "./assets/temp_result.json"
|
||||
import POSE_3D_MANY_ from "./assets/many_people_all_3d_pose.json"
|
||||
import POSE_3D_04_02_ from "./assets/res.json"
|
||||
// import POSE_3D_ from "./assets/temp_result.json"
|
||||
// import POSE_3D_MANY_ from "./assets/many_people_all_3d_pose.json"
|
||||
import POSE_3D_04_02_ from "/home/admin/Code/CVTH3PE/samples/Test_QuanCheng.json"
|
||||
|
||||
import optical3dPointsRaw from "/home/admin/Code/CVTH3PE/samples/optical_3d_points.json"
|
||||
|
||||
// 133, 3
|
||||
type PosePoints3D = [number, number, number][]
|
||||
@ -17,13 +20,33 @@ type PosePoints3D = [number, number, number][]
|
||||
type AnimePosePoints3D = PosePoints3D[]
|
||||
|
||||
interface Skeleton0402 {
|
||||
"a": PosePoints3D
|
||||
"b": PosePoints3D
|
||||
"1": PosePoints3D[],
|
||||
"4": PosePoints3D[],
|
||||
// "b": PosePoints3D
|
||||
}
|
||||
|
||||
const POSE_3D = POSE_3D_ as AnimePosePoints3D
|
||||
const POSE_3D_MANY = POSE_3D_MANY_ as AnimePosePoints3D[] // N F 133 3
|
||||
interface CameraDescriptor {
|
||||
transformation_matrx: number[] // 16 (4x4)
|
||||
camera_matrix: number[] // 9 (3x3)
|
||||
}
|
||||
|
||||
// 扩展 Drei 的 TextProps 接口
|
||||
interface TextProps extends DreiTextProps {
|
||||
position?: [number, number, number];
|
||||
fontSize?: number;
|
||||
color?: string;
|
||||
billboard?: boolean;
|
||||
}
|
||||
|
||||
// 创建类型安全的 Text 组件
|
||||
const Text = forwardRef<THREE.Mesh, TextProps>((props, ref) => {
|
||||
return <DreiText {...props} ref={ref} />;
|
||||
});
|
||||
|
||||
// const POSE_3D = POSE_3D_ as AnimePosePoints3D
|
||||
// const POSE_3D_MANY = POSE_3D_MANY_ as AnimePosePoints3D[] // N F 133 3
|
||||
const POSE_3D_04_02 = POSE_3D_04_02_ as Skeleton0402
|
||||
// const POSE_3D_04_02 = POSE_3D_04_02_ as AnimePosePoints3D
|
||||
|
||||
const THREE_ADDONS = {
|
||||
FontLoader,
|
||||
@ -111,41 +134,92 @@ const DEFAULT_TRANSFORMATION_MATRIX = [
|
||||
const DEFAULT_NEAR = 0.05
|
||||
const DEFAULT_FAR = 1
|
||||
const CAMERA_EXTRINSIC_MATRIX_MAP: Record<string, number[]> = {
|
||||
"AE_01": [
|
||||
0.37408302, -0.91907411, 0.12395429, 1.18976111, 0.17243349,
|
||||
-0.06239751, -0.98304285, -0.06429779, 0.91122367, 0.38911351,
|
||||
0.13513731, 2.51940833, 0., 0., 0.,
|
||||
1.
|
||||
|
||||
"5601": [
|
||||
0.6356, 0.0095, 0.772, -0.4025, -0.0361, -0.9985, 0.042,
|
||||
0.0892, 0.7712, -0.0546, -0.6343, 5.0822, 0., 0.,
|
||||
0., 1.
|
||||
] as const,
|
||||
"AE_1A": [
|
||||
0.92998171, -0.36696694, -0.02166301, 2.21643671, -0.05110403,
|
||||
-0.07070226, -0.99618752, -0.72948697, 0.36403626, 0.92754324,
|
||||
-0.0845053, 6.45800206, 0., 0., 0.,
|
||||
1.
|
||||
"5602": [
|
||||
0.0638, 0.0474, -0.9968, -0.4955, 0.1185, -0.9922, -0.0396,
|
||||
0.1454, -0.9909, -0.1156, -0.0689, 5.0618, 0., 0.,
|
||||
0., 1.
|
||||
] as const,
|
||||
"AE_08": [
|
||||
0.98195914, -0.18888337, -0.00890642, 1.43011854, -0.02247979,
|
||||
-0.06984105, -0.99730481, -0.61678831, 0.18775226, 0.97951279,
|
||||
-0.07282712, 5.81983825, 0., 0., 0.,
|
||||
1.
|
||||
] as const
|
||||
"5603": [
|
||||
-0.5541, -0.0196, 0.8322, 0.9785, -0.0576, -0.9964, -0.0619,
|
||||
-0.0225, 0.8304, -0.0823, 0.551, 6.8712, 0., 0.,
|
||||
0., 1.
|
||||
] as const,
|
||||
"5604": [
|
||||
0.0448, -0.0137, 0.9989, 0.165, -0.0717, -0.9974, -0.0105,
|
||||
0.2167, 0.9964, -0.0712, -0.0457, 5.1217, 0., 0.,
|
||||
0., 1.
|
||||
] as const,
|
||||
"5605": [
|
||||
-0.6795, 0.0297, -0.733, -0.7666, 0.0113, -0.9986, -0.051,
|
||||
0.4234, -0.7335, -0.043, 0.6783, 6.1889, 0., 0.,
|
||||
0., 1.
|
||||
] as const,
|
||||
"5606": [
|
||||
0.6502, 0.0654, -0.757, -0.2844, 0.216, -0.9711, 0.1016,
|
||||
-0.8482, -0.7285, -0.2296, -0.6455, 5.2607, 0., 0.,
|
||||
0., 1.
|
||||
] as const,
|
||||
"5607": [
|
||||
0.8406, 0.0516, -0.5393, -0.3939, -0.0916, -0.9676, -0.2354,
|
||||
0.2677, -0.5339, 0.2473, -0.8086, 2.2953, 0., 0.,
|
||||
0., 1.
|
||||
] as const,
|
||||
"5608": [
|
||||
0.93, 0.0182, 0.3672, 0.6156, 0.0133, -0.9998, 0.0159,
|
||||
0.1594, 0.3674, -0.0099, -0.93, 7.6299, 0., 0.,
|
||||
0., 1.
|
||||
] as const,
|
||||
"5609": [
|
||||
0.8488, 0.0485, -0.5265, -0.1715, 0.0611, -0.9981, 0.0065,
|
||||
-0.0113, -0.5252, -0.0377, -0.8502, 6.8236, 0., 0.,
|
||||
0., 1.
|
||||
] as const,
|
||||
|
||||
}
|
||||
const CAMERA_INTRINSIC_MATRIX_MAP: Record<string, number[]> = {
|
||||
"AE_01": [
|
||||
1806.82137617, 0., 1230.53175624, 0.,
|
||||
1809.75580378, 766.36204406, 0., 0.,
|
||||
1.
|
||||
"5601": [
|
||||
2686.004, 0., 1470.4911, 0., 2699.927, 765.5127,
|
||||
0., 0., 1.
|
||||
] as const,
|
||||
"AE_1A": [
|
||||
3467.39715751, 0., 1000.62548655, 0.,
|
||||
3473.7168112, 831.64048503, 0., 0.,
|
||||
1.
|
||||
"5602": [
|
||||
2686.004, 0., 1470.4911, 0., 2699.927, 765.5127,
|
||||
0., 0., 1.
|
||||
] as const,
|
||||
"5603": [
|
||||
2791.3838, 0., 1258.1116, 0., 2790.6707, 788.1486,
|
||||
0., 0., 1.
|
||||
] as const,
|
||||
"5604": [
|
||||
2789.2568, 0., 1231.131, 0., 2787.0845, 677.6938,
|
||||
0., 0., 1.
|
||||
] as const,
|
||||
"5605": [
|
||||
2644.6814, 0., 1285.3489, 0., 2644.9702, 627.2081,
|
||||
0., 0., 1.
|
||||
] as const,
|
||||
"5606": [
|
||||
1919.8364, 0., 1201.1659, 0., 1908.0964, 982.5976,
|
||||
0., 0., 1.
|
||||
] as const,
|
||||
"5607": [
|
||||
1806.8214, 0., 1230.5317, 0., 1809.7559, 766.3621,
|
||||
0., 0., 1.
|
||||
] as const,
|
||||
"5608": [
|
||||
|
||||
3467.3972, 0., 1000.6255, 0., 3473.7168, 831.6405,
|
||||
0., 0., 1.
|
||||
] as const,
|
||||
"5609": [
|
||||
2785.4392, 0., 1254.9827, 0., 2788.1045, 738.8298,
|
||||
0., 0., 1.
|
||||
] as const,
|
||||
"AE_08": [
|
||||
2785.43931794, 0., 1254.98272372, 0.,
|
||||
2788.10437965, 738.82985324, 0., 0.,
|
||||
1.
|
||||
] as const
|
||||
}
|
||||
|
||||
const IMAGE_WIDTH = 2560
|
||||
@ -163,7 +237,112 @@ const intrinsicToFov = (intrinsic: number[], image_size: { width: number, height
|
||||
return { fov_x, fov_y }
|
||||
}
|
||||
|
||||
const calculaterCubeVersices = (position:number[], dimensions: number[])=>{
|
||||
const [cx, cy,cz] = position
|
||||
const [width, height, depth] = dimensions
|
||||
const halfWidth = width / 2
|
||||
const halfHeight = height / 2
|
||||
const halfDepth = depth / 2
|
||||
|
||||
return [
|
||||
[cx - halfWidth, cy - halfHeight, cz - halfDepth],
|
||||
[cx + halfWidth, cy - halfHeight, cz - halfDepth],
|
||||
[cx + halfWidth, cy + halfHeight, cz - halfDepth],
|
||||
[cx - halfWidth, cy + halfHeight, cz - halfDepth],
|
||||
[cx - halfWidth, cy - halfHeight, cz + halfDepth],
|
||||
[cx + halfWidth, cy - halfHeight, cz + halfDepth],
|
||||
[cx + halfWidth, cy + halfHeight, cz + halfDepth],
|
||||
[cx - halfWidth, cy + halfHeight, cz + halfDepth]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
const Scene = () => {
|
||||
|
||||
// 处理 optical_3d_points.json 数据,转换为点数组
|
||||
const optical3dPoints: [number, number, number][] = (optical3dPointsRaw as any[]).map(
|
||||
(item) => Array.isArray(item) && Array.isArray(item[0]) ? item[0] as [number, number, number] : item as [number, number, number]
|
||||
)
|
||||
|
||||
// 绘制静态3D曲线组件
|
||||
const Optical3DLine = () => {
|
||||
if (!optical3dPoints || optical3dPoints.length < 2) return null;
|
||||
// 转为three.js Vector3数组
|
||||
const points = optical3dPoints.map(([x, y, z]) => new THREE.Vector3(x, y, z));
|
||||
const curve = new THREE.CatmullRomCurve3(points);
|
||||
const curvePoints = curve.getPoints(optical3dPoints.length * 5); // 插值更平滑
|
||||
// 生成 position 属性的 Float32Array
|
||||
const positions = new Float32Array(curvePoints.length * 3);
|
||||
curvePoints.forEach((v, i) => {
|
||||
positions[i * 3] = v.x;
|
||||
positions[i * 3 + 1] = v.y;
|
||||
positions[i * 3 + 2] = v.z;
|
||||
});
|
||||
return (
|
||||
<>
|
||||
{/* 曲线 */}
|
||||
<line>
|
||||
<bufferGeometry>
|
||||
<bufferAttribute args={[positions, 3]} />
|
||||
</bufferGeometry>
|
||||
<lineBasicMaterial color="#ff00ff" linewidth={2} />
|
||||
</line>
|
||||
{/* 端点小球 */}
|
||||
{points.map((p, i) => (
|
||||
<mesh key={i} position={[p.x, p.y, p.z]}>
|
||||
<sphereGeometry args={[0.015, 12, 12]} />
|
||||
<meshStandardMaterial color="#ff00ff" />
|
||||
</mesh>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
// 定义立方体绘制组件
|
||||
const Cube = () =>{
|
||||
|
||||
const vertices = calculaterCubeVersices([0.205+0.2 ,0.205+0.50,-0.205-0.45],[0.65,1.8,1])
|
||||
|
||||
return (
|
||||
<>
|
||||
<mesh position={[0.205+0.2 ,0.205+0.50,-0.205-0.45]}> {/** 原点位置,相对于六面体中心偏移 */}
|
||||
<boxGeometry args={[0.65,1.8,1]} /> {/** 边长,宽:1.5米,高:1.5米,深度:1米 */}
|
||||
<meshStandardMaterial
|
||||
color="#007BFF" // 蓝色
|
||||
opacity={0.2} // 半透明
|
||||
transparent={true}
|
||||
wireframe={true} // 线框模式,仅显示边框
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* 顶点标记(带坐标系和文本) */}
|
||||
{vertices.map(([x, y, z], index) => (
|
||||
<group key={index} position={[x, y, z]}>
|
||||
{/* 顶点处的小标记点 */}
|
||||
<mesh>
|
||||
<sphereGeometry args={[0.05, 16, 16]} />
|
||||
<meshBasicMaterial color="#ff0000" />
|
||||
</mesh>
|
||||
|
||||
{/* 坐标轴(X:红, Y:绿, Z:蓝) */}
|
||||
<axesHelper args={[0.2]} />
|
||||
|
||||
{/* 坐标文本(始终面向相机) */}
|
||||
<Text
|
||||
position={[0, 0.1, 0]} // 文本在顶点上方
|
||||
fontSize={0.08}
|
||||
color="#007Bff"
|
||||
billboard
|
||||
>
|
||||
{`P${index}: (${x.toFixed(2)}, ${y.toFixed(2)}, ${z.toFixed(2)})`}
|
||||
</Text>
|
||||
</group>
|
||||
))}
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function Floor() {
|
||||
return (
|
||||
<mesh rotation-x={-Math.PI / 2} position-y={-0.5} receiveShadow>
|
||||
@ -253,7 +432,7 @@ const Scene = () => {
|
||||
const cameraCvt = CV_TO_GL_MAT.clone()
|
||||
|
||||
// Convert from Z-up to Y-up first (this affects world coordinates)
|
||||
const worldCvt = Z_UP_TO_Y_UP.clone()
|
||||
// const worldCvt = Z_UP_TO_Y_UP.clone()
|
||||
|
||||
// Final transformation:
|
||||
// 1. Convert world from Z-up to Y-up
|
||||
@ -263,7 +442,7 @@ const Scene = () => {
|
||||
final
|
||||
.multiply(cameraCvt)
|
||||
.multiply(Rt)
|
||||
.multiply(worldCvt)
|
||||
// .multiply(worldCvt)
|
||||
|
||||
// Invert to get the camera-to-world transform
|
||||
final.invert()
|
||||
@ -329,10 +508,14 @@ const Scene = () => {
|
||||
|
||||
// Transform a joint position using the coordinate system conversion
|
||||
const transformJointPosition = (j: [number, number, number]) => {
|
||||
// const [x, y, z] = j
|
||||
// const V = new THREE.Vector3(x, y, z)
|
||||
// const worldCvt = Z_UP_TO_Y_UP_PRIME.clone()
|
||||
// V.applyMatrix4(worldCvt)
|
||||
// return V
|
||||
|
||||
const [x, y, z] = j
|
||||
const V = new THREE.Vector3(x, y, z)
|
||||
const worldCvt = Z_UP_TO_Y_UP_PRIME.clone()
|
||||
V.applyMatrix4(worldCvt)
|
||||
return V
|
||||
}
|
||||
|
||||
@ -423,8 +606,8 @@ const Scene = () => {
|
||||
// <Human3DSkeleton jointRadius={0.005} boneRadius={0.0025} frameRate={24} skeleton={POSE_3D} />,
|
||||
// ]
|
||||
const skeletons = [
|
||||
<Human3DSkeleton jointRadius={0.005} boneRadius={0.0025} frameRate={24} skeleton={[POSE_3D_04_02.a]} />,
|
||||
<Human3DSkeleton jointRadius={0.005} boneRadius={0.0025} frameRate={24} skeleton={[POSE_3D_04_02.b]} />,
|
||||
<Human3DSkeleton jointRadius={0.05} boneRadius={0.025} frameRate={24} skeleton={POSE_3D_04_02[1]} />,
|
||||
// <Human3DSkeleton jointRadius={0.05} boneRadius={0.025} frameRate={24} skeleton={[POSE_3D_04_02.b]} />,
|
||||
]
|
||||
|
||||
const cameras = Object.entries(CAMERA_EXTRINSIC_MATRIX_MAP).map(([key, value]) => {
|
||||
@ -435,6 +618,7 @@ const Scene = () => {
|
||||
return <CameraViewFromExtrinsic key={key} name={`${key}(${fov_x.toFixed(1)})`} extrinsic={preProcessExtrinsic(value)} fov={fov_x} aspect={IMAGE_WIDTH / IMAGE_HEIGHT} far={far} />
|
||||
})
|
||||
|
||||
// 在场景中添加立方体
|
||||
const scene = (<group>
|
||||
{/* <OrbitControls /> */}
|
||||
<ambientLight intensity={0.05} />
|
||||
@ -442,6 +626,8 @@ const Scene = () => {
|
||||
{/* <Floor /> */}
|
||||
{ }
|
||||
<Axes />
|
||||
<Cube /> {/** 新增立方体 */}
|
||||
<Optical3DLine /> {/** 新增静态3D曲线 */}
|
||||
{cameras}
|
||||
{skeletons}
|
||||
</group>)
|
||||
|
||||
634
src/Boxing.tsx
Normal file
634
src/Boxing.tsx
Normal file
@ -0,0 +1,634 @@
|
||||
import { Grid, useBVH, useGLTF, CameraControls, AccumulativeShadows, OrbitControls, Stats } from '@react-three/drei'
|
||||
import { Camera, Canvas, useFrame, useThree, useLoader, RenderCallback, RootState } from '@react-three/fiber'
|
||||
import { Text as DreiText, TextProps as DreiTextProps } from '@react-three/drei';
|
||||
import * as THREE from 'three'
|
||||
import { FontLoader } from 'three/addons/loaders/FontLoader.js'
|
||||
import { TextGeometry } from 'three/addons/geometries/TextGeometry.js'
|
||||
import HelvetikerRegular from "three/examples/fonts/helvetiker_regular.typeface.json"
|
||||
import { useEffect, useRef, useState, JSX, forwardRef } from 'react'
|
||||
import Boxing from "./assets/Test_WeiHua_Segment_1.json"
|
||||
|
||||
// 133, 3
|
||||
type PosePoints3D = [number, number, number][]
|
||||
|
||||
// F, 133, 3
|
||||
type AnimePosePoints3D = PosePoints3D[]
|
||||
|
||||
interface Skeleton {
|
||||
"1": PosePoints3D[],
|
||||
"2": PosePoints3D[],
|
||||
"3": PosePoints3D[],
|
||||
}
|
||||
|
||||
// 扩展 Drei 的 TextProps 接口
|
||||
interface TextProps extends DreiTextProps {
|
||||
position?: [number, number, number];
|
||||
fontSize?: number;
|
||||
color?: string;
|
||||
billboard?: boolean;
|
||||
}
|
||||
|
||||
// 创建类型安全的 Text 组件
|
||||
const Text = forwardRef<THREE.Mesh, TextProps>((props, ref) => {
|
||||
return <DreiText {...props} ref={ref} />;
|
||||
});
|
||||
|
||||
// const POSE_3D = POSE_3D_ as AnimePosePoints3D
|
||||
// const POSE_3D_MANY = POSE_3D_MANY_ as AnimePosePoints3D[] // N F 133 3
|
||||
const BOXING = Boxing as Skeleton
|
||||
|
||||
const THREE_ADDONS = {
|
||||
FontLoader,
|
||||
TextGeometry,
|
||||
} as const
|
||||
|
||||
// Create OpenCV to OpenGL conversion matrix
|
||||
// OpenCV: X right, Y down, Z forward
|
||||
// OpenGL: X right, Y up, Z backward
|
||||
const CV_TO_GL_MAT = new THREE.Matrix4().set(
|
||||
1, 0, 0, 0,
|
||||
0, -1, 0, 0,
|
||||
0, 0, -1, 0,
|
||||
0, 0, 0, 1
|
||||
)
|
||||
|
||||
// Z-up to Y-up conversion matrix
|
||||
// Rotate -90 degrees around X axis to convert from Z-up to Y-up
|
||||
const Z_UP_TO_Y_UP = new THREE.Matrix4().set(
|
||||
-1, 0, 0, 0,
|
||||
0, 0, -1, 0,
|
||||
0, -1, 0, 0,
|
||||
0, 0, 0, 1
|
||||
)
|
||||
|
||||
const Z_UP_TO_Y_UP_PRIME = new THREE.Matrix4().set(
|
||||
1, 0, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 0, 1
|
||||
)
|
||||
|
||||
// Color definitions for different body parts
|
||||
const COLOR_SPINE = new THREE.Color(138 / 255, 201 / 255, 38 / 255) // green, spine & head
|
||||
const COLOR_ARMS = new THREE.Color(255 / 255, 202 / 255, 58 / 255) // yellow, arms & shoulders
|
||||
const COLOR_LEGS = new THREE.Color(25 / 255, 130 / 255, 196 / 255) // blue, legs & hips
|
||||
const COLOR_FINGERS = new THREE.Color(255 / 255, 0, 0) // red, fingers
|
||||
const COLOR_FACE = new THREE.Color(255 / 255, 200 / 255, 0) // yellow, face
|
||||
const COLOR_FOOT = new THREE.Color(255 / 255, 128 / 255, 0) // orange, foot
|
||||
const COLOR_HEAD = new THREE.Color(255 / 255, 0, 255 / 255) // purple, head
|
||||
|
||||
// Body bone connections
|
||||
const BODY_BONES = [
|
||||
// legs
|
||||
[15, 13], [13, 11], [16, 14], [14, 12], [11, 12], // legs
|
||||
[5, 11], [6, 12], [5, 6], // torso
|
||||
[5, 7], [7, 9], [6, 8], [8, 10], // arms
|
||||
[1, 2], [0, 1], [0, 2], [1, 3], [2, 4], // head
|
||||
[15, 17], [15, 18], [15, 19], // left foot
|
||||
[16, 20], [16, 21], [16, 22], // right foot
|
||||
] as const
|
||||
|
||||
// Body bone colors
|
||||
const BODY_BONE_COLORS = [
|
||||
COLOR_LEGS, COLOR_LEGS, COLOR_LEGS, COLOR_LEGS, COLOR_LEGS,
|
||||
COLOR_SPINE, COLOR_SPINE, COLOR_SPINE,
|
||||
COLOR_ARMS, COLOR_ARMS, COLOR_ARMS, COLOR_ARMS,
|
||||
COLOR_HEAD, COLOR_HEAD, COLOR_HEAD, COLOR_HEAD, COLOR_HEAD,
|
||||
COLOR_FOOT, COLOR_FOOT, COLOR_FOOT,
|
||||
COLOR_FOOT, COLOR_FOOT, COLOR_FOOT,
|
||||
] as const
|
||||
|
||||
// Hand bone connections (in pairs of [start, end] indices)
|
||||
const HAND_BONES = [
|
||||
// right hand
|
||||
[91, 92], [92, 93], [93, 94], [94, 95], // right thumb
|
||||
[91, 96], [96, 97], [97, 98], [98, 99], // right index
|
||||
[91, 100], [100, 101], [101, 102], [102, 103], // right middle
|
||||
[91, 104], [104, 105], [105, 106], [106, 107], // right ring
|
||||
[91, 108], [108, 109], [109, 110], [110, 111], // right pinky
|
||||
// left hand
|
||||
[112, 113], [113, 114], [114, 115], [115, 116], // left thumb
|
||||
[112, 117], [117, 118], [118, 119], [119, 120], // left index
|
||||
[112, 121], [121, 122], [122, 123], [123, 124], // left middle
|
||||
[112, 125], [125, 126], [126, 127], [127, 128], // left ring
|
||||
[112, 129], [129, 130], [130, 131], [131, 132] // left pinky
|
||||
] as const
|
||||
|
||||
const DEFAULT_TRANSFORMATION_MATRIX = [
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1,
|
||||
] as const
|
||||
const DEFAULT_NEAR = 0.05
|
||||
const DEFAULT_FAR = 1
|
||||
const CAMERA_EXTRINSIC_MATRIX_MAP: Record<string, number[]> = {
|
||||
"5602": [
|
||||
-0.76138617, 0.16195241, 0.62774399, -0.73832425, -0.40758532,
|
||||
-0.87257285, -0.26924121, 0.55561231, 0.5041481, -0.46085577,
|
||||
0.73037432, 3.28663985, 0., 0., 0.,
|
||||
1.
|
||||
] as const,
|
||||
"5603": [
|
||||
-0.44966325, -0.02806161, -0.89275725, -0.24260155, 0.21789368,
|
||||
-0.97275592, -0.07917234, 0.41197614, -0.8662132, -0.23012705,
|
||||
0.44352704, 4.37769458, 0., 0., 0.,
|
||||
1.
|
||||
] as const,
|
||||
"5604": [
|
||||
0.9551736, -0.02735669, 0.29477958, 0.5908996, -0.06148677,
|
||||
-0.99234061, 0.10714238, 0.87629594, 0.28959069, -0.12046462,
|
||||
-0.94953963, 4.1617598, 0., 0., 0.,
|
||||
1.
|
||||
] as const,
|
||||
"5605": [
|
||||
0.57140284, 0.03118154, -0.82007713, -0.22679438, 0.11021058,
|
||||
-0.99314166, 0.0390292, 0.83904835, -0.81323577, -0.11268257,
|
||||
-0.5709205, 4.17730229, 0., 0., 0.,
|
||||
1.
|
||||
] as const,
|
||||
}
|
||||
const CAMERA_INTRINSIC_MATRIX_MAP: Record<string, number[]> = {
|
||||
"5602": [
|
||||
2686.00393399, 0., 1470.49106388, 0.,
|
||||
2699.92688641, 765.51266883, 0., 0.,
|
||||
1.
|
||||
] as const,
|
||||
"5603": [
|
||||
2791.38378418, 0., 1258.11161208, 0.,
|
||||
2790.67070205, 788.14860021, 0., 0.,
|
||||
1.
|
||||
] as const,
|
||||
"5604": [
|
||||
2789.25680621, 0., 1231.13101573, 0.,
|
||||
2787.08458106, 677.69385417, 0., 0.,
|
||||
1.
|
||||
] as const,
|
||||
"5605": [
|
||||
2644.68145454, 0., 1285.34889127, 0.,
|
||||
2644.9700955, 627.20808584, 0., 0.,
|
||||
1.
|
||||
] as const,
|
||||
}
|
||||
|
||||
const IMAGE_WIDTH = 2560
|
||||
const IMAGE_HEIGHT = 1440
|
||||
|
||||
const intrinsicToFov = (intrinsic: number[], image_size: { width: number, height: number }) => {
|
||||
console.assert(intrinsic.length === 9, "intrinsic must be a 3x3 matrix")
|
||||
const fx = intrinsic[0]
|
||||
const fy = intrinsic[4]
|
||||
const cx = intrinsic[2]
|
||||
const cy = intrinsic[5]
|
||||
// in degrees
|
||||
const fov_x = 2 * Math.atan(image_size.width / (2 * fx)) * (180 / Math.PI)
|
||||
const fov_y = 2 * Math.atan(image_size.height / (2 * fy)) * (180 / Math.PI)
|
||||
return { fov_x, fov_y }
|
||||
}
|
||||
|
||||
const calculaterCubeVersices = (position: number[], dimensions: number[]) => {
|
||||
const [cx, cy, cz] = position
|
||||
const [width, height, depth] = dimensions
|
||||
const halfWidth = width / 2
|
||||
const halfHeight = height / 2
|
||||
const halfDepth = depth / 2
|
||||
|
||||
return [
|
||||
[cx - halfWidth, cy - halfHeight, cz - halfDepth],
|
||||
[cx + halfWidth, cy - halfHeight, cz - halfDepth],
|
||||
[cx + halfWidth, cy + halfHeight, cz - halfDepth],
|
||||
[cx - halfWidth, cy + halfHeight, cz - halfDepth],
|
||||
[cx - halfWidth, cy - halfHeight, cz + halfDepth],
|
||||
[cx + halfWidth, cy - halfHeight, cz + halfDepth],
|
||||
[cx + halfWidth, cy + halfHeight, cz + halfDepth],
|
||||
[cx - halfWidth, cy + halfHeight, cz + halfDepth]
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
const Scene = () => {
|
||||
|
||||
// 定义立方体绘制组件
|
||||
const Cube = () => {
|
||||
|
||||
const vertices = calculaterCubeVersices([0, -0.205 + 0.9, 0.90], [0.5, 1.8, 0.5])
|
||||
|
||||
return (
|
||||
<>
|
||||
<mesh position={[0, -0.205 + 0.9, 0.90]}> {/** 原点位置,相对于六面体中心偏移 */}
|
||||
<boxGeometry args={[0.5, 1.8, 0.5]} /> {/** 边长,宽:0.5米,高:1.8米,深度:0.5米 */}
|
||||
<meshStandardMaterial
|
||||
color="#007BFF" // 蓝色
|
||||
opacity={0.2} // 半透明
|
||||
transparent={true}
|
||||
wireframe={true} // 线框模式,仅显示边框
|
||||
/>
|
||||
</mesh>
|
||||
|
||||
{/* 顶点标记(带坐标系和文本) */}
|
||||
{vertices.map(([x, y, z], index) => (
|
||||
<group key={index} position={[x, y, z]}>
|
||||
{/* 顶点处的小标记点 */}
|
||||
<mesh>
|
||||
<sphereGeometry args={[0.0, 16, 16]} />
|
||||
<meshBasicMaterial color="#ff0000" />
|
||||
</mesh>
|
||||
|
||||
{/* 坐标轴(X:红, Y:绿, Z:蓝) */}
|
||||
<axesHelper args={[0.2]} />
|
||||
|
||||
{/* 沙袋上的坐标文本(始终面向相机) */}
|
||||
{/* <Text
|
||||
position={[0, 0.1, 0]} // 文本在顶点上方
|
||||
fontSize={0.08}
|
||||
color="#007Bff"
|
||||
billboard
|
||||
>
|
||||
{`P${index}: (${x.toFixed(2)}, ${y.toFixed(2)}, ${z.toFixed(2)})`}
|
||||
</Text> */}
|
||||
</group>
|
||||
))}
|
||||
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function Floor() {
|
||||
return (
|
||||
<mesh rotation-x={-Math.PI / 2} position-y={-0.5} receiveShadow>
|
||||
<planeGeometry args={[15, 15]} />
|
||||
<meshStandardMaterial color="#ccc" />
|
||||
</mesh>
|
||||
)
|
||||
}
|
||||
const Axes = () => {
|
||||
return <axesHelper args={[15]} />
|
||||
}
|
||||
interface CameraViewFromExtrinsicProps {
|
||||
extrinsic: number[] | THREE.Matrix4
|
||||
aspect?: number
|
||||
name?: string
|
||||
near?: number
|
||||
far?: number
|
||||
fov?: number
|
||||
textSize?: number
|
||||
}
|
||||
// https://threejs.org/docs/#examples/en/loaders/FontLoader
|
||||
// https://www.ilyameerovich.com/simple-3d-text-meshes-in-three-js/
|
||||
|
||||
const CameraViewFromExtrinsic = ({ extrinsic, name, near, far, fov, textSize, aspect }: CameraViewFromExtrinsicProps) => {
|
||||
let Rt: THREE.Matrix4
|
||||
if (extrinsic instanceof THREE.Matrix4) {
|
||||
Rt = extrinsic
|
||||
} else if (Array.isArray(extrinsic)) {
|
||||
console.assert(extrinsic.length === 16, "extrinsic must be a 4x4 matrix")
|
||||
Rt = new THREE.Matrix4()
|
||||
// @ts-expect-error 16 elements
|
||||
Rt.set(...extrinsic)
|
||||
} else {
|
||||
throw new Error("extrinsic must be a 4x4 matrix or an array of 16 elements")
|
||||
}
|
||||
const font = new FontLoader().parse(HelvetikerRegular)
|
||||
const camera = new THREE.PerspectiveCamera(fov ?? 60, aspect ?? 4 / 3, near ?? DEFAULT_NEAR, far ?? DEFAULT_FAR)
|
||||
const helper = <cameraHelper args={[camera]} />
|
||||
camera.applyMatrix4(Rt)
|
||||
|
||||
const textRef = useRef<THREE.Mesh>(null)
|
||||
const { camera: viewCamera } = useThree()
|
||||
|
||||
useFrame(() => {
|
||||
if (textRef.current) {
|
||||
textRef.current.lookAt(viewCamera.position)
|
||||
}
|
||||
})
|
||||
|
||||
let text: JSX.Element | null = null
|
||||
if (name) {
|
||||
const geo = new THREE_ADDONS.TextGeometry(name ?? "", { font, size: textSize ?? 0.1, depth: 0.001 })
|
||||
const position = new THREE.Vector3()
|
||||
position.setFromMatrixPosition(Rt)
|
||||
|
||||
text = (
|
||||
<mesh ref={textRef} position={position}>
|
||||
<primitive object={geo} />
|
||||
<meshStandardMaterial color="black" />
|
||||
</mesh>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<group>
|
||||
{text}
|
||||
<primitive object={camera} />
|
||||
{helper}
|
||||
</group>
|
||||
)
|
||||
}
|
||||
|
||||
const preProcessExtrinsic = (extrinsic: number[] | THREE.Matrix4) => {
|
||||
let Rt: THREE.Matrix4
|
||||
if (extrinsic instanceof THREE.Matrix4) {
|
||||
Rt = extrinsic
|
||||
} else if (Array.isArray(extrinsic)) {
|
||||
console.assert(extrinsic.length === 16, "extrinsic must be a 4x4 matrix")
|
||||
Rt = new THREE.Matrix4()
|
||||
// @ts-expect-error 16 elements
|
||||
Rt.set(...extrinsic)
|
||||
} else {
|
||||
throw new Error("extrinsic must be a 4x4 matrix or an array of 16 elements")
|
||||
}
|
||||
|
||||
|
||||
// Then handle OpenCV to OpenGL camera convention
|
||||
const cameraCvt = CV_TO_GL_MAT.clone()
|
||||
|
||||
// Convert from Z-up to Y-up first (this affects world coordinates)
|
||||
// const worldCvt = Z_UP_TO_Y_UP.clone()
|
||||
|
||||
// Final transformation:
|
||||
// 1. Convert world from Z-up to Y-up
|
||||
// 2. Apply the camera transform
|
||||
// 3. Convert camera coordinates from OpenCV to OpenGL
|
||||
const final = new THREE.Matrix4()
|
||||
final
|
||||
.multiply(cameraCvt)
|
||||
.multiply(Rt)
|
||||
// .multiply(worldCvt)
|
||||
|
||||
// Invert to get the camera-to-world transform
|
||||
final.invert()
|
||||
return final
|
||||
}
|
||||
|
||||
interface Human3DSkeletonProps {
|
||||
skeleton: AnimePosePoints3D
|
||||
startFrame?: number
|
||||
jointRadius?: number
|
||||
boneRadius?: number
|
||||
showJoints?: boolean
|
||||
showBones?: boolean
|
||||
frameRate?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 3D人体骨架动画组件
|
||||
* @param skeleton 动画帧序列,每帧为133个关节点的三维坐标
|
||||
* @param startFrame 起始帧
|
||||
* @param jointRadius 关节点球体半径
|
||||
* @param boneRadius 骨骼圆柱体半径
|
||||
* @param showJoints 是否显示关节点
|
||||
* @param showBones 是否显示骨骼
|
||||
* @param frameRate 动画帧率
|
||||
*/
|
||||
const Human3DSkeleton = ({
|
||||
skeleton,
|
||||
startFrame = 0,
|
||||
jointRadius = 0.01,
|
||||
boneRadius = 0.005,
|
||||
showJoints = true,
|
||||
showBones = true,
|
||||
frameRate = 24
|
||||
}: Human3DSkeletonProps) => {
|
||||
// 当前动画帧索引
|
||||
const [frameIndex, setFrameIndex] = useState(startFrame)
|
||||
const totalFrames = skeleton.length
|
||||
// 动画帧推进函数,按frameRate自动推进
|
||||
const onFrame: RenderCallback = (totalFrames === 0) ? (state, delta) => { } : (state: RootState, delta: number) => {
|
||||
// 根据帧率和delta推进帧索引,实现动画
|
||||
setFrameIndex(prevFrame => {
|
||||
const nextFrame = prevFrame + frameRate * delta
|
||||
// 到末尾自动循环
|
||||
return nextFrame >= totalFrames ? 0 : nextFrame
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
// 注册动画帧推进
|
||||
useFrame(onFrame)
|
||||
|
||||
// 当前帧的133个关节点坐标
|
||||
const currentFrame = Math.floor(frameIndex) % totalFrames
|
||||
const joints = skeleton[currentFrame]
|
||||
|
||||
/**
|
||||
* 右腕(关键点10)速度计算
|
||||
* 速度 = (当前帧位置 - 上一帧位置) / (1 / frameRate)
|
||||
* 单位:与坐标单位一致/秒
|
||||
*/
|
||||
let wristSpeed: number | null = null;
|
||||
if (currentFrame > 0 && joints && joints.length > 16) {
|
||||
const prevJoints = skeleton[(currentFrame - 1 + totalFrames) % totalFrames];
|
||||
if (prevJoints && prevJoints.length > 16) {
|
||||
const wNow = joints[10];
|
||||
const wPrev = prevJoints[10];
|
||||
if (wNow && wPrev) {
|
||||
const dx = wNow[0] - wPrev[0];
|
||||
const dy = wNow[1] - wPrev[1];
|
||||
const dz = wNow[2] - wPrev[2];
|
||||
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
wristSpeed = dist * frameRate; // 单位/秒
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 右手大臂(右肩-右肘)和小臂(右肘-右腕)夹角计算
|
||||
* COCO/BlazePose: 右肩=6, 右肘=8, 右腕=10
|
||||
* 夹角为两向量的夹角,单位度
|
||||
*/
|
||||
let rightUpperArmAngle: number | null = null;
|
||||
if (joints && joints.length > 16) {
|
||||
const shoulder = joints[6]; // 右肩
|
||||
const elbow = joints[8]; // 右肘
|
||||
const wrist = joints[10]; // 右腕
|
||||
if (shoulder && elbow && wrist) {
|
||||
// 大臂向量(肩->肘)
|
||||
const v1 = new THREE.Vector3(
|
||||
shoulder[0] - elbow[0],
|
||||
shoulder[1] - elbow[1],
|
||||
shoulder[2] - elbow[2]
|
||||
);
|
||||
// 小臂向量(腕->肘)
|
||||
const v2 = new THREE.Vector3(
|
||||
wrist[0] - elbow[0],
|
||||
wrist[1] - elbow[1],
|
||||
wrist[2] - elbow[2]
|
||||
);
|
||||
// 计算夹角
|
||||
const dot = v1.dot(v2);
|
||||
const len1 = v1.length();
|
||||
const len2 = v2.length();
|
||||
if (len1 > 1e-6 && len2 > 1e-6) {
|
||||
const angle = Math.acos(Math.max(-1, Math.min(1, dot / (len1 * len2))));
|
||||
rightUpperArmAngle = angle * 180 / Math.PI;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关节点颜色
|
||||
* @param idx 关节点索引
|
||||
*/
|
||||
const getJointColor = (idx: number) => {
|
||||
// 脸部
|
||||
if (idx >= 23 && idx <= 90) return COLOR_FACE
|
||||
// 手指
|
||||
if (idx >= 91 && idx <= 132) return COLOR_FINGERS
|
||||
// 脚
|
||||
if (idx >= 17 && idx <= 22) return COLOR_FOOT
|
||||
// 头部
|
||||
if (idx <= 4) return COLOR_HEAD
|
||||
// 手臂
|
||||
if (idx >= 5 && idx <= 10) return COLOR_ARMS
|
||||
// 腿
|
||||
if (idx >= 11 && idx <= 16) return COLOR_LEGS
|
||||
// 躯干
|
||||
return COLOR_SPINE
|
||||
}
|
||||
|
||||
/**
|
||||
* 关节点坐标转换(如需坐标系变换可在此处理)
|
||||
*/
|
||||
const transformJointPosition = (j: [number, number, number]) => {
|
||||
const [x, y, z] = j
|
||||
const V = new THREE.Vector3(x, y, z)
|
||||
return V
|
||||
}
|
||||
|
||||
// 生成关节点球体
|
||||
const jointMeshes = showJoints ? joints.map((j, idx) => {
|
||||
const position = transformJointPosition(j)
|
||||
const color = getJointColor(idx)
|
||||
return (
|
||||
<mesh key={`joint-${idx}`} position={position}>
|
||||
<sphereGeometry args={[jointRadius, 16, 16]} />
|
||||
<meshStandardMaterial color={color} />
|
||||
</mesh>
|
||||
)
|
||||
}) : null
|
||||
|
||||
// 生成骨骼圆柱体
|
||||
const boneMeshes = showBones ? (
|
||||
<>
|
||||
{/* 身体主骨骼 */}
|
||||
{BODY_BONES.map((bone, idx) => {
|
||||
const [startIdx, endIdx] = bone
|
||||
if (startIdx >= joints.length || endIdx >= joints.length) return null
|
||||
// 起止点
|
||||
const startPos = transformJointPosition(joints[startIdx])
|
||||
const endPos = transformJointPosition(joints[endIdx])
|
||||
const color = BODY_BONE_COLORS[idx]
|
||||
// 骨骼中点和长度
|
||||
const midpoint = new THREE.Vector3().addVectors(startPos, endPos).multiplyScalar(0.5)
|
||||
const length = startPos.distanceTo(endPos)
|
||||
// 旋转对齐
|
||||
const direction = new THREE.Vector3().subVectors(endPos, startPos).normalize()
|
||||
const quaternion = new THREE.Quaternion()
|
||||
const up = new THREE.Vector3(0, 1, 0)
|
||||
quaternion.setFromUnitVectors(up, direction)
|
||||
return (
|
||||
<mesh key={`bone-body-${idx}`} position={midpoint} quaternion={quaternion}>
|
||||
<cylinderGeometry args={[boneRadius, boneRadius, length, 8]} />
|
||||
<meshStandardMaterial color={color} />
|
||||
</mesh>
|
||||
)
|
||||
})}
|
||||
{/* 手部骨骼 */}
|
||||
{HAND_BONES.map((bone, idx) => {
|
||||
const [startIdx, endIdx] = bone
|
||||
if (startIdx >= joints.length || endIdx >= joints.length) return null
|
||||
const startPos = transformJointPosition(joints[startIdx])
|
||||
const endPos = transformJointPosition(joints[endIdx])
|
||||
const midpoint = new THREE.Vector3().addVectors(startPos, endPos).multiplyScalar(0.5)
|
||||
const length = startPos.distanceTo(endPos)
|
||||
const direction = new THREE.Vector3().subVectors(endPos, startPos).normalize()
|
||||
const quaternion = new THREE.Quaternion()
|
||||
const up = new THREE.Vector3(0, 1, 0)
|
||||
quaternion.setFromUnitVectors(up, direction)
|
||||
return (
|
||||
<mesh key={`bone-hand-${idx}`} position={midpoint} quaternion={quaternion}>
|
||||
<cylinderGeometry args={[boneRadius, boneRadius, length, 8]} />
|
||||
<meshStandardMaterial color={COLOR_FINGERS} />
|
||||
</mesh>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
) : null
|
||||
|
||||
// 渲染骨架、关节点、骨骼、右臂夹角和右腕速度文本
|
||||
return (
|
||||
<group>
|
||||
{jointMeshes}
|
||||
{boneMeshes}
|
||||
{/* 实时显示右手大臂和小臂夹角,文本位于右肘上方 */}
|
||||
{rightUpperArmAngle !== null && joints[8] && (
|
||||
<Text
|
||||
position={[1.5, 2.5, 0]}
|
||||
fontSize={0.3}
|
||||
color="#ff00ff"
|
||||
billboard
|
||||
>
|
||||
{`右臂夹角: ${rightUpperArmAngle.toFixed(1)}°`}
|
||||
</Text>
|
||||
)}
|
||||
{/* 实时显示右腕速度,文本位于右腕上方 */}
|
||||
{wristSpeed !== null && joints[10] && (
|
||||
<Text
|
||||
position={[-1.5, 2.5, 0]}
|
||||
fontSize={0.3}
|
||||
color="#00bfff"
|
||||
billboard
|
||||
>
|
||||
{`右腕速度: ${wristSpeed.toFixed(3)}`}
|
||||
</Text>
|
||||
)}
|
||||
</group>
|
||||
)
|
||||
}
|
||||
|
||||
// 调小关节点和骨骼的半径
|
||||
const skeletons = [
|
||||
<Human3DSkeleton jointRadius={0.03} boneRadius={0.014} frameRate={24} skeleton={BOXING[1]} />,
|
||||
]
|
||||
|
||||
const cameras = Object.entries(CAMERA_EXTRINSIC_MATRIX_MAP).map(([key, value]) => {
|
||||
const intrinsic = CAMERA_INTRINSIC_MATRIX_MAP[key]
|
||||
const { fov_x, fov_y } = intrinsicToFov(intrinsic, { width: IMAGE_WIDTH, height: IMAGE_HEIGHT })
|
||||
// make the far reverse proportional to the fov
|
||||
const far = (1 / fov_x) * 20
|
||||
return <CameraViewFromExtrinsic key={key} name={`${key}(${fov_x.toFixed(1)})`} extrinsic={preProcessExtrinsic(value)} fov={fov_x} aspect={IMAGE_WIDTH / IMAGE_HEIGHT} far={far} />
|
||||
})
|
||||
|
||||
// 在场景中添加立方体
|
||||
const scene = (<group>
|
||||
{/* <OrbitControls /> */}
|
||||
<ambientLight intensity={0.05} />
|
||||
<directionalLight castShadow position={[3.3, 6, 4.4]} intensity={5} />
|
||||
{/* <Floor /> */}
|
||||
{ }
|
||||
<Axes />
|
||||
<Cube /> {/** 新增立方体 */}
|
||||
{cameras}
|
||||
{skeletons}
|
||||
</group>)
|
||||
return (
|
||||
// Note that we don't need to import anything, All three.js objects will be treated
|
||||
// as native JSX elements, just like you can just write <div /> or <span /> in
|
||||
// regular ReactDOM. The general rule is that Fiber components are available under
|
||||
// the camel-case version of their name in three.js.
|
||||
<>
|
||||
<CameraControls />
|
||||
<Stats />
|
||||
{scene}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Canvas shadows style={{ background: "#e9e9e9", width: "100vw", height: "100vh" }}>
|
||||
<Scene />
|
||||
</Canvas>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
538
src/Yeu_305.tsx
Normal file
538
src/Yeu_305.tsx
Normal file
@ -0,0 +1,538 @@
|
||||
import { Grid, useBVH, useGLTF, CameraControls, AccumulativeShadows, OrbitControls, Stats } from '@react-three/drei'
|
||||
import { Camera, Canvas, useFrame, useThree, useLoader, RenderCallback, RootState } from '@react-three/fiber'
|
||||
import { Text as DreiText, TextProps as DreiTextProps } from '@react-three/drei';
|
||||
import * as THREE from 'three'
|
||||
import { FontLoader } from 'three/addons/loaders/FontLoader.js'
|
||||
import { TextGeometry } from 'three/addons/geometries/TextGeometry.js'
|
||||
import HelvetikerRegular from "three/examples/fonts/helvetiker_regular.typeface.json"
|
||||
import { useEffect, useRef, useState, JSX, forwardRef } from 'react'
|
||||
import OBJECT from "/home/admin/Code/CVTH3PE/samples/Test_YEU.json"
|
||||
|
||||
|
||||
// 133, 3
|
||||
type PosePoints3D = [number, number, number][]
|
||||
|
||||
// F, 133, 3
|
||||
type AnimePosePoints3D = PosePoints3D[]
|
||||
|
||||
interface Skeleton {
|
||||
1: PosePoints3D[],
|
||||
2: PosePoints3D[],
|
||||
}
|
||||
|
||||
// 扩展 Drei 的 TextProps 接口
|
||||
interface TextProps extends DreiTextProps {
|
||||
position?: [number, number, number];
|
||||
fontSize?: number;
|
||||
color?: string;
|
||||
billboard?: boolean;
|
||||
}
|
||||
|
||||
// 创建类型安全的 Text 组件
|
||||
const Text = forwardRef<THREE.Mesh, TextProps>((props, ref) => {
|
||||
return <DreiText {...props} ref={ref} />;
|
||||
});
|
||||
|
||||
// const POSE_3D = POSE_3D_ as AnimePosePoints3D
|
||||
// const POSE_3D_MANY = POSE_3D_MANY_ as AnimePosePoints3D[] // N F 133 3
|
||||
const yeu_object = OBJECT as Skeleton
|
||||
|
||||
const THREE_ADDONS = {
|
||||
FontLoader,
|
||||
TextGeometry,
|
||||
} as const
|
||||
|
||||
// Create OpenCV to OpenGL conversion matrix
|
||||
// OpenCV: X right, Y down, Z forward
|
||||
// OpenGL: X right, Y up, Z backward
|
||||
const CV_TO_GL_MAT = new THREE.Matrix4().set(
|
||||
1, 0, 0, 0,
|
||||
0, -1, 0, 0,
|
||||
0, 0, -1, 0,
|
||||
0, 0, 0, 1
|
||||
)
|
||||
|
||||
// Z-up to Y-up conversion matrix
|
||||
// Rotate -90 degrees around X axis to convert from Z-up to Y-up
|
||||
const Z_UP_TO_Y_UP = new THREE.Matrix4().set(
|
||||
-1, 0, 0, 0,
|
||||
0, 0, -1, 0,
|
||||
0, -1, 0, 0,
|
||||
0, 0, 0, 1
|
||||
)
|
||||
|
||||
const Z_UP_TO_Y_UP_PRIME = new THREE.Matrix4().set(
|
||||
1, 0, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 0, 1
|
||||
)
|
||||
|
||||
// Color definitions for different body parts
|
||||
const COLOR_SPINE = new THREE.Color(138 / 255, 201 / 255, 38 / 255) // green, spine & head
|
||||
const COLOR_ARMS = new THREE.Color(255 / 255, 202 / 255, 58 / 255) // yellow, arms & shoulders
|
||||
const COLOR_LEGS = new THREE.Color(25 / 255, 130 / 255, 196 / 255) // blue, legs & hips
|
||||
const COLOR_FINGERS = new THREE.Color(255 / 255, 0, 0) // red, fingers
|
||||
const COLOR_FACE = new THREE.Color(255 / 255, 200 / 255, 0) // yellow, face
|
||||
const COLOR_FOOT = new THREE.Color(255 / 255, 128 / 255, 0) // orange, foot
|
||||
const COLOR_HEAD = new THREE.Color(255 / 255, 0, 255 / 255) // purple, head
|
||||
|
||||
// Body bone connections
|
||||
const BODY_BONES = [
|
||||
// legs
|
||||
[15, 13], [13, 11], [16, 14], [14, 12], [11, 12], // legs
|
||||
[5, 11], [6, 12], [5, 6], // torso
|
||||
[5, 7], [7, 9], [6, 8], [8, 10], // arms
|
||||
[1, 2], [0, 1], [0, 2], [1, 3], [2, 4], // head
|
||||
[15, 17], [15, 18], [15, 19], // left foot
|
||||
[16, 20], [16, 21], [16, 22], // right foot
|
||||
] as const
|
||||
|
||||
// Body bone colors
|
||||
const BODY_BONE_COLORS = [
|
||||
COLOR_LEGS, COLOR_LEGS, COLOR_LEGS, COLOR_LEGS, COLOR_LEGS,
|
||||
COLOR_SPINE, COLOR_SPINE, COLOR_SPINE,
|
||||
COLOR_ARMS, COLOR_ARMS, COLOR_ARMS, COLOR_ARMS,
|
||||
COLOR_HEAD, COLOR_HEAD, COLOR_HEAD, COLOR_HEAD, COLOR_HEAD,
|
||||
COLOR_FOOT, COLOR_FOOT, COLOR_FOOT,
|
||||
COLOR_FOOT, COLOR_FOOT, COLOR_FOOT,
|
||||
] as const
|
||||
|
||||
// Hand bone connections (in pairs of [start, end] indices)
|
||||
const HAND_BONES = [
|
||||
// right hand
|
||||
[91, 92], [92, 93], [93, 94], [94, 95], // right thumb
|
||||
[91, 96], [96, 97], [97, 98], [98, 99], // right index
|
||||
[91, 100], [100, 101], [101, 102], [102, 103], // right middle
|
||||
[91, 104], [104, 105], [105, 106], [106, 107], // right ring
|
||||
[91, 108], [108, 109], [109, 110], [110, 111], // right pinky
|
||||
// left hand
|
||||
[112, 113], [113, 114], [114, 115], [115, 116], // left thumb
|
||||
[112, 117], [117, 118], [118, 119], [119, 120], // left index
|
||||
[112, 121], [121, 122], [122, 123], [123, 124], // left middle
|
||||
[112, 125], [125, 126], [126, 127], [127, 128], // left ring
|
||||
[112, 129], [129, 130], [130, 131], [131, 132] // left pinky
|
||||
] as const
|
||||
|
||||
const DEFAULT_TRANSFORMATION_MATRIX = [
|
||||
1, 0, 0, 0,
|
||||
0, 1, 0, 0,
|
||||
0, 0, 1, 0,
|
||||
0, 0, 0, 1,
|
||||
] as const
|
||||
const DEFAULT_NEAR = 0.05
|
||||
const DEFAULT_FAR = 1
|
||||
const CAMERA_EXTRINSIC_MATRIX_MAP: Record<string, number[]> = {
|
||||
"5602": [
|
||||
-0.76138617, 0.16195241, 0.62774399, -0.73832425, -0.40758532,
|
||||
-0.87257285, -0.26924121, 0.55561231, 0.5041481, -0.46085577,
|
||||
0.73037432, 3.28663985, 0., 0., 0.,
|
||||
1.
|
||||
] as const,
|
||||
"5603": [
|
||||
-0.44966325, -0.02806161, -0.89275725, -0.24260155, 0.21789368,
|
||||
-0.97275592, -0.07917234, 0.41197614, -0.8662132, -0.23012705,
|
||||
0.44352704, 4.37769458, 0., 0., 0.,
|
||||
1.
|
||||
] as const,
|
||||
"5604": [
|
||||
0.9551736, -0.02735669, 0.29477958, 0.5908996, -0.06148677,
|
||||
-0.99234061, 0.10714238, 0.87629594, 0.28959069, -0.12046462,
|
||||
-0.94953963, 4.1617598, 0., 0., 0.,
|
||||
1.
|
||||
] as const,
|
||||
"5605": [
|
||||
0.57140284, 0.03118154, -0.82007713, -0.22679438, 0.11021058,
|
||||
-0.99314166, 0.0390292, 0.83904835, -0.81323577, -0.11268257,
|
||||
-0.5709205, 4.17730229, 0., 0., 0.,
|
||||
1.
|
||||
] as const,
|
||||
}
|
||||
const CAMERA_INTRINSIC_MATRIX_MAP: Record<string, number[]> = {
|
||||
"5602": [
|
||||
2686.00393399, 0., 1470.49106388, 0.,
|
||||
2699.92688641, 765.51266883, 0., 0.,
|
||||
1.
|
||||
] as const,
|
||||
"5603": [
|
||||
2791.38378418, 0., 1258.11161208, 0.,
|
||||
2790.67070205, 788.14860021, 0., 0.,
|
||||
1.
|
||||
] as const,
|
||||
"5604": [
|
||||
2789.25680621, 0., 1231.13101573, 0.,
|
||||
2787.08458106, 677.69385417, 0., 0.,
|
||||
1.
|
||||
] as const,
|
||||
"5605": [
|
||||
2644.68145454, 0., 1285.34889127, 0.,
|
||||
2644.9700955, 627.20808584, 0., 0.,
|
||||
1.
|
||||
] as const,
|
||||
}
|
||||
|
||||
const IMAGE_WIDTH = 2560
|
||||
const IMAGE_HEIGHT = 1440
|
||||
|
||||
const intrinsicToFov = (intrinsic: number[], image_size: { width: number, height: number }) => {
|
||||
console.assert(intrinsic.length === 9, "intrinsic must be a 3x3 matrix")
|
||||
const fx = intrinsic[0]
|
||||
const fy = intrinsic[4]
|
||||
const cx = intrinsic[2]
|
||||
const cy = intrinsic[5]
|
||||
// in degrees
|
||||
const fov_x = 2 * Math.atan(image_size.width / (2 * fx)) * (180 / Math.PI)
|
||||
const fov_y = 2 * Math.atan(image_size.height / (2 * fy)) * (180 / Math.PI)
|
||||
return { fov_x, fov_y }
|
||||
}
|
||||
|
||||
const Scene = () => {
|
||||
const Axes = () => {
|
||||
return <axesHelper args={[15]} />
|
||||
}
|
||||
interface CameraViewFromExtrinsicProps {
|
||||
extrinsic: number[] | THREE.Matrix4
|
||||
aspect?: number
|
||||
name?: string
|
||||
near?: number
|
||||
far?: number
|
||||
fov?: number
|
||||
textSize?: number
|
||||
}
|
||||
// https://threejs.org/docs/#examples/en/loaders/FontLoader
|
||||
// https://www.ilyameerovich.com/simple-3d-text-meshes-in-three-js/
|
||||
|
||||
const CameraViewFromExtrinsic = ({ extrinsic, name, near, far, fov, textSize, aspect }: CameraViewFromExtrinsicProps) => {
|
||||
let Rt: THREE.Matrix4
|
||||
if (extrinsic instanceof THREE.Matrix4) {
|
||||
Rt = extrinsic
|
||||
} else if (Array.isArray(extrinsic)) {
|
||||
console.assert(extrinsic.length === 16, "extrinsic must be a 4x4 matrix")
|
||||
Rt = new THREE.Matrix4()
|
||||
// @ts-expect-error 16 elements
|
||||
Rt.set(...extrinsic)
|
||||
} else {
|
||||
throw new Error("extrinsic must be a 4x4 matrix or an array of 16 elements")
|
||||
}
|
||||
const font = new FontLoader().parse(HelvetikerRegular)
|
||||
const camera = new THREE.PerspectiveCamera(fov ?? 60, aspect ?? 4 / 3, near ?? DEFAULT_NEAR, far ?? DEFAULT_FAR)
|
||||
const helper = <cameraHelper args={[camera]} />
|
||||
camera.applyMatrix4(Rt)
|
||||
|
||||
const textRef = useRef<THREE.Mesh>(null)
|
||||
const { camera: viewCamera } = useThree()
|
||||
|
||||
useFrame(() => {
|
||||
if (textRef.current) {
|
||||
textRef.current.lookAt(viewCamera.position)
|
||||
}
|
||||
})
|
||||
|
||||
let text: JSX.Element | null = null
|
||||
if (name) {
|
||||
const geo = new THREE_ADDONS.TextGeometry(name ?? "", { font, size: textSize ?? 0.1, depth: 0.001 })
|
||||
const position = new THREE.Vector3()
|
||||
position.setFromMatrixPosition(Rt)
|
||||
|
||||
text = (
|
||||
<mesh ref={textRef} position={position}>
|
||||
<primitive object={geo} />
|
||||
<meshStandardMaterial color="black" />
|
||||
</mesh>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<group>
|
||||
{text}
|
||||
<primitive object={camera} />
|
||||
{helper}
|
||||
</group>
|
||||
)
|
||||
}
|
||||
|
||||
const preProcessExtrinsic = (extrinsic: number[] | THREE.Matrix4) => {
|
||||
let Rt: THREE.Matrix4
|
||||
if (extrinsic instanceof THREE.Matrix4) {
|
||||
Rt = extrinsic
|
||||
} else if (Array.isArray(extrinsic)) {
|
||||
console.assert(extrinsic.length === 16, "extrinsic must be a 4x4 matrix")
|
||||
Rt = new THREE.Matrix4()
|
||||
// @ts-expect-error 16 elements
|
||||
Rt.set(...extrinsic)
|
||||
} else {
|
||||
throw new Error("extrinsic must be a 4x4 matrix or an array of 16 elements")
|
||||
}
|
||||
|
||||
|
||||
// Then handle OpenCV to OpenGL camera convention
|
||||
const cameraCvt = CV_TO_GL_MAT.clone()
|
||||
|
||||
// Convert from Z-up to Y-up first (this affects world coordinates)
|
||||
// const worldCvt = Z_UP_TO_Y_UP.clone()
|
||||
|
||||
// Final transformation:
|
||||
// 1. Convert world from Z-up to Y-up
|
||||
// 2. Apply the camera transform
|
||||
// 3. Convert camera coordinates from OpenCV to OpenGL
|
||||
const final = new THREE.Matrix4()
|
||||
final
|
||||
.multiply(cameraCvt)
|
||||
.multiply(Rt)
|
||||
// .multiply(worldCvt)
|
||||
|
||||
// Invert to get the camera-to-world transform
|
||||
final.invert()
|
||||
return final
|
||||
}
|
||||
|
||||
interface Human3DSkeletonProps {
|
||||
skeleton: AnimePosePoints3D
|
||||
startFrame?: number
|
||||
jointRadius?: number
|
||||
boneRadius?: number
|
||||
showJoints?: boolean
|
||||
showBones?: boolean
|
||||
frameRate?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 3D人体骨架动画组件
|
||||
* @param skeleton 动画帧序列,每帧为133个关节点的三维坐标
|
||||
* @param startFrame 起始帧
|
||||
* @param jointRadius 关节点球体半径
|
||||
* @param boneRadius 骨骼圆柱体半径
|
||||
* @param showJoints 是否显示关节点
|
||||
* @param showBones 是否显示骨骼
|
||||
* @param frameRate 动画帧率
|
||||
*/
|
||||
const Human3DSkeleton = ({
|
||||
skeleton,
|
||||
startFrame = 0,
|
||||
jointRadius = 0.01,
|
||||
boneRadius = 0.005,
|
||||
showJoints = true,
|
||||
showBones = true,
|
||||
frameRate = 24
|
||||
}: Human3DSkeletonProps) => {
|
||||
// 当前动画帧索引
|
||||
const [frameIndex, setFrameIndex] = useState(startFrame)
|
||||
const totalFrames = skeleton.length
|
||||
// 动画帧推进函数,按frameRate自动推进
|
||||
const onFrame: RenderCallback = (totalFrames === 0) ? (state, delta) => { } : (state: RootState, delta: number) => {
|
||||
// 根据帧率和delta推进帧索引,实现动画
|
||||
setFrameIndex(prevFrame => {
|
||||
const nextFrame = prevFrame + frameRate * delta
|
||||
// 到末尾自动循环
|
||||
return nextFrame >= totalFrames ? 0 : nextFrame
|
||||
})
|
||||
return null
|
||||
}
|
||||
|
||||
// 注册动画帧推进
|
||||
useFrame(onFrame)
|
||||
|
||||
// 当前帧的133个关节点坐标
|
||||
const currentFrame = Math.floor(frameIndex) % totalFrames
|
||||
const joints = skeleton[currentFrame]
|
||||
|
||||
/**
|
||||
* 右腕(关键点10)速度计算
|
||||
* 速度 = (当前帧位置 - 上一帧位置) / (1 / frameRate)
|
||||
* 单位:与坐标单位一致/秒
|
||||
*/
|
||||
let wristSpeed: number | null = null;
|
||||
if (currentFrame > 0 && joints && joints.length > 16) {
|
||||
const prevJoints = skeleton[(currentFrame - 1 + totalFrames) % totalFrames];
|
||||
if (prevJoints && prevJoints.length > 16) {
|
||||
const wNow = joints[10];
|
||||
const wPrev = prevJoints[10];
|
||||
if (wNow && wPrev) {
|
||||
const dx = wNow[0] - wPrev[0];
|
||||
const dy = wNow[1] - wPrev[1];
|
||||
const dz = wNow[2] - wPrev[2];
|
||||
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
||||
wristSpeed = dist * frameRate; // 单位/秒
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 右手大臂(右肩-右肘)和小臂(右肘-右腕)夹角计算
|
||||
* COCO/BlazePose: 右肩=6, 右肘=8, 右腕=10
|
||||
* 夹角为两向量的夹角,单位度
|
||||
*/
|
||||
let rightUpperArmAngle: number | null = null;
|
||||
if (joints && joints.length > 16) {
|
||||
const shoulder = joints[6]; // 右肩
|
||||
const elbow = joints[8]; // 右肘
|
||||
const wrist = joints[10]; // 右腕
|
||||
if (shoulder && elbow && wrist) {
|
||||
// 大臂向量(肩->肘)
|
||||
const v1 = new THREE.Vector3(
|
||||
shoulder[0] - elbow[0],
|
||||
shoulder[1] - elbow[1],
|
||||
shoulder[2] - elbow[2]
|
||||
);
|
||||
// 小臂向量(腕->肘)
|
||||
const v2 = new THREE.Vector3(
|
||||
wrist[0] - elbow[0],
|
||||
wrist[1] - elbow[1],
|
||||
wrist[2] - elbow[2]
|
||||
);
|
||||
// 计算夹角
|
||||
const dot = v1.dot(v2);
|
||||
const len1 = v1.length();
|
||||
const len2 = v2.length();
|
||||
if (len1 > 1e-6 && len2 > 1e-6) {
|
||||
const angle = Math.acos(Math.max(-1, Math.min(1, dot / (len1 * len2))));
|
||||
rightUpperArmAngle = angle * 180 / Math.PI;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取关节点颜色
|
||||
* @param idx 关节点索引
|
||||
*/
|
||||
const getJointColor = (idx: number) => {
|
||||
// 脸部
|
||||
if (idx >= 23 && idx <= 90) return COLOR_FACE
|
||||
// 手指
|
||||
if (idx >= 91 && idx <= 132) return COLOR_FINGERS
|
||||
// 脚
|
||||
if (idx >= 17 && idx <= 22) return COLOR_FOOT
|
||||
// 头部
|
||||
if (idx <= 4) return COLOR_HEAD
|
||||
// 手臂
|
||||
if (idx >= 5 && idx <= 10) return COLOR_ARMS
|
||||
// 腿
|
||||
if (idx >= 11 && idx <= 16) return COLOR_LEGS
|
||||
// 躯干
|
||||
return COLOR_SPINE
|
||||
}
|
||||
|
||||
/**
|
||||
* 关节点坐标转换(如需坐标系变换可在此处理)
|
||||
*/
|
||||
const transformJointPosition = (j: [number, number, number]) => {
|
||||
const [x, y, z] = j
|
||||
const V = new THREE.Vector3(x, y, z)
|
||||
return V
|
||||
}
|
||||
|
||||
// 生成关节点球体
|
||||
const jointMeshes = showJoints ? joints.map((j, idx) => {
|
||||
const position = transformJointPosition(j)
|
||||
const color = getJointColor(idx)
|
||||
return (
|
||||
<mesh key={`joint-${idx}`} position={position}>
|
||||
<sphereGeometry args={[jointRadius, 16, 16]} />
|
||||
<meshStandardMaterial color={color} />
|
||||
</mesh>
|
||||
)
|
||||
}) : null
|
||||
|
||||
// 生成骨骼圆柱体
|
||||
const boneMeshes = showBones ? (
|
||||
<>
|
||||
{/* 身体主骨骼 */}
|
||||
{BODY_BONES.map((bone, idx) => {
|
||||
const [startIdx, endIdx] = bone
|
||||
if (startIdx >= joints.length || endIdx >= joints.length) return null
|
||||
// 起止点
|
||||
const startPos = transformJointPosition(joints[startIdx])
|
||||
const endPos = transformJointPosition(joints[endIdx])
|
||||
const color = BODY_BONE_COLORS[idx]
|
||||
// 骨骼中点和长度
|
||||
const midpoint = new THREE.Vector3().addVectors(startPos, endPos).multiplyScalar(0.5)
|
||||
const length = startPos.distanceTo(endPos)
|
||||
// 旋转对齐
|
||||
const direction = new THREE.Vector3().subVectors(endPos, startPos).normalize()
|
||||
const quaternion = new THREE.Quaternion()
|
||||
const up = new THREE.Vector3(0, 1, 0)
|
||||
quaternion.setFromUnitVectors(up, direction)
|
||||
return (
|
||||
<mesh key={`bone-body-${idx}`} position={midpoint} quaternion={quaternion}>
|
||||
<cylinderGeometry args={[boneRadius, boneRadius, length, 8]} />
|
||||
<meshStandardMaterial color={color} />
|
||||
</mesh>
|
||||
)
|
||||
})}
|
||||
{/* 手部骨骼 */}
|
||||
{HAND_BONES.map((bone, idx) => {
|
||||
const [startIdx, endIdx] = bone
|
||||
if (startIdx >= joints.length || endIdx >= joints.length) return null
|
||||
const startPos = transformJointPosition(joints[startIdx])
|
||||
const endPos = transformJointPosition(joints[endIdx])
|
||||
const midpoint = new THREE.Vector3().addVectors(startPos, endPos).multiplyScalar(0.5)
|
||||
const length = startPos.distanceTo(endPos)
|
||||
const direction = new THREE.Vector3().subVectors(endPos, startPos).normalize()
|
||||
const quaternion = new THREE.Quaternion()
|
||||
const up = new THREE.Vector3(0, 1, 0)
|
||||
quaternion.setFromUnitVectors(up, direction)
|
||||
return (
|
||||
<mesh key={`bone-hand-${idx}`} position={midpoint} quaternion={quaternion}>
|
||||
<cylinderGeometry args={[boneRadius, boneRadius, length, 8]} />
|
||||
<meshStandardMaterial color={COLOR_FINGERS} />
|
||||
</mesh>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
) : null
|
||||
|
||||
// 渲染骨架、关节点、骨骼、右臂夹角和右腕速度文本
|
||||
return (
|
||||
<group>
|
||||
{jointMeshes}
|
||||
{boneMeshes}
|
||||
</group>
|
||||
)
|
||||
}
|
||||
|
||||
// 调小关节点和骨骼的半径
|
||||
const skeletons = [
|
||||
<Human3DSkeleton jointRadius={0.03} boneRadius={0.014} frameRate={24} skeleton={yeu_object[1]} />,
|
||||
<Human3DSkeleton jointRadius={0.03} boneRadius={0.014} frameRate={24} skeleton={yeu_object[2]} />,
|
||||
]
|
||||
|
||||
const cameras = Object.entries(CAMERA_EXTRINSIC_MATRIX_MAP).map(([key, value]) => {
|
||||
const intrinsic = CAMERA_INTRINSIC_MATRIX_MAP[key]
|
||||
const { fov_x, fov_y } = intrinsicToFov(intrinsic, { width: IMAGE_WIDTH, height: IMAGE_HEIGHT })
|
||||
// make the far reverse proportional to the fov
|
||||
const far = (1 / fov_x) * 20
|
||||
return <CameraViewFromExtrinsic key={key} name={`${key}(${fov_x.toFixed(1)})`} extrinsic={preProcessExtrinsic(value)} fov={fov_x} aspect={IMAGE_WIDTH / IMAGE_HEIGHT} far={far} />
|
||||
})
|
||||
|
||||
// 在场景中添加立方体
|
||||
const scene = (<group>
|
||||
{/* <OrbitControls /> */}
|
||||
<ambientLight intensity={0.05} />
|
||||
<directionalLight castShadow position={[3.3, 6, 4.4]} intensity={5} />
|
||||
{/* <Floor /> */}
|
||||
{ }
|
||||
<Axes />
|
||||
{cameras}
|
||||
{skeletons}
|
||||
</group>)
|
||||
return (
|
||||
// Note that we don't need to import anything, All three.js objects will be treated
|
||||
// as native JSX elements, just like you can just write <div /> or <span /> in
|
||||
// regular ReactDOM. The general rule is that Fiber components are available under
|
||||
// the camel-case version of their name in three.js.
|
||||
<>
|
||||
<CameraControls />
|
||||
<Stats />
|
||||
{scene}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Canvas shadows style={{ background: "#e9e9e9", width: "100vw", height: "100vh" }}>
|
||||
<Scene />
|
||||
</Canvas>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
@ -3,10 +3,14 @@ import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
import { Leva } from 'leva'
|
||||
import Boxing from './Boxing.tsx'
|
||||
import Yeu_305 from './Yeu_305.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<Leva />
|
||||
<App />
|
||||
{/* <App /> */}
|
||||
<Boxing />
|
||||
{/* <Yeu_305 /> */}
|
||||
</StrictMode>,
|
||||
)
|
||||
|
||||
23
src/test.cpp
Normal file
23
src/test.cpp
Normal file
@ -0,0 +1,23 @@
|
||||
#include <print>
|
||||
|
||||
int main(){
|
||||
auto a = 10;
|
||||
auto b = 20;
|
||||
|
||||
|
||||
struct capture_object_t {
|
||||
int a;
|
||||
int b;
|
||||
int sum(){
|
||||
return a+b;
|
||||
}
|
||||
}; // object
|
||||
|
||||
auto sum = [a, b]() mutable {
|
||||
return a+b;
|
||||
}; // closure
|
||||
|
||||
auto sum_obj = capture_object_t{.a = a, .b = b};
|
||||
std::print("a={},b={},sum={},obj={}; a={}",a,b,sum(),sum_obj.sum(), a);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user