Compare commits

..

11 Commits

Author SHA1 Message Date
dbe3662088 Update App.tsx to introduce a new pose data structure for skeleton animation, enhancing the Human3DSkeleton component to accept multiple skeletons. Adjust type definitions and update skeleton instances in the Scene for improved flexibility in pose rendering. 2025-04-17 11:55:33 +08:00
42c5ff6540 Refactor App.tsx to enhance skeleton animation capabilities by introducing a new pose data structure and updating the Human3DSkeleton component to accept customizable skeletons. Adjust camera extrinsic parameters for improved scene rendering. 2025-04-16 10:24:45 +08:00
0d8786f471 x 2025-03-28 11:23:05 +08:00
d4e86e60ff Update .gitignore to exclude JSON files in src/assets and modify import path for pose data in App.tsx to reflect new asset structure. 2025-03-27 16:16:22 +08:00
8bcefd93bc Refactor Human3DSkeleton component to support frame-based animation, allowing for customizable start frame and frame rate. Remove deprecated Joints component for cleaner code. Update instance in Scene to reflect new props. 2025-03-27 16:14:27 +08:00
eaee23df40 Enhance Human3DSkeleton component to visualize 3D skeleton with color-coded joints and bones. Introduce body and hand bone connections, and update joint rendering logic to use specific colors based on joint types. Maintain compatibility with the previous Joints component. 2025-03-27 16:08:01 +08:00
2ec78b5d74 Add Joints component to visualize 3D pose data in Scene. Import pose data from JSON and implement Z-up to Y-up transformation for joint positioning. Update .gitignore to exclude public JSON files. 2025-03-27 16:02:00 +08:00
d46b5ca3d6 Update Z-up to Y-up conversion matrix in App.tsx to correct axis orientation. Remove default camera view rendering from Scene component to streamline camera setup. 2025-03-26 10:19:35 +08:00
6406dbd19f Add comprehensive documentation on coordinate system conversions in note.md. Explain the necessity and order of transformations for world and camera coordinates, clarifying the distinction between Z-up to Y-up and OpenCV to OpenGL conversions. 2025-03-25 10:22:26 +08:00
9fb7235d93 Refactor preProcessExtrinsic function to handle both Matrix4 and array inputs for extrinsic parameters. Add error handling for invalid inputs and update transformation sequence for camera-to-world conversion. 2025-03-25 10:17:08 +08:00
885ddd0989 Add detailed transformation sequence documentation in note.md and implement Z-up to Y-up conversion matrix in App.tsx. Update preProcessExtrinsic function to correctly apply transformation order for camera-to-world conversion. 2025-03-25 10:10:16 +08:00
3 changed files with 468 additions and 37 deletions

2
.gitignore vendored
View File

@ -22,3 +22,5 @@ dist-ssr
*.njsproj
*.sln
*.sw?
public/*.json
src/assets/*.json

162
note.md Normal file
View File

@ -0,0 +1,162 @@
I'll write down the transformation sequence using LaTeX notation. Let me break down the complete operation:
For a point $P$ in the original world space, the final transformation can be written as:
$$
P_{final} = (C_{GL} \cdot E \cdot W_{Y})^{-1} \cdot P
$$
Where:
- $E$ is your original extrinsic matrix (world-to-camera transform)
- $W_{Y}$ is the Z-up to Y-up world conversion matrix
- $C_{GL}$ is the OpenCV to OpenGL camera conversion matrix
- $^{-1}$ denotes matrix inversion
Breaking down each matrix:
$$
W_{Y} = \begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 \\
0 & -1 & 0 & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
$$
$$
C_{GL} = \begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & -1 & 0 & 0 \\
0 & 0 & -1 & 0 \\
0 & 0 & 0 & 1
\end{bmatrix}
$$
The order of operations (from right to left) is:
1. Convert world from Z-up to Y-up ($W_{Y}$)
2. Apply camera transform ($E$)
3. Convert camera space to OpenGL convention ($C_{GL}$)
4. Invert the whole transformation ($^{-1}$)
This matches our code where we write:
```typescript
finalTransform
.multiply(cameraConversion) // C_GL
.multiply(worldToCamera) // E
.multiply(worldZupToYup) // W_Y
.invert() // ^{-1}
```
Let me explain the reasoning behind this order:
1. First, $W_Y$ (Z-up to Y-up):
- We need to convert the world coordinates BEFORE applying the camera transform
- Because your original extrinsic matrix $E$ was defined in Z-up world coordinates
- If we didn't do this first, the camera transform would be applied in the wrong coordinate system
2. Then, $E$ (your extrinsic matrix):
- This transforms points from the (now Y-up) world space into camera space
- The camera space at this point is still in OpenCV convention
3. Finally, $C_{GL}$ (OpenCV to OpenGL):
- This converts the camera space from OpenCV convention to OpenGL convention
- It must be applied AFTER the camera transform because it's converting the camera's view space
- If we applied it before, we'd be incorrectly modifying the world coordinates
Think about it like this:
1. We first need to fix the world coordinate system to match Three.js ($W_Y$)
2. Then we can apply your camera's view of this world ($E$)
3. Finally, we adapt how the camera "sees" to match OpenGL conventions ($C_{GL}$)
If we did it in a different order:
- Applying $C_{GL}$ before $E$ would incorrectly transform the world coordinates instead of the camera space
- Applying $W_Y$ after $E$ would transform the camera space instead of the world coordinates
- The transformations would effectively be applied to the wrong spaces
The inversion at the end ($^{-1}$) is needed because Three.js expects camera-to-world transforms rather than world-to-camera transforms.
Ah yes, you're absolutely right! I made a mistake in my explanation. In matrix multiplication, the operations are indeed applied from right to left.
When we write:
$$
P_{final} = (C_{GL} \cdot E \cdot W_{Y})^{-1} \cdot P
$$
The actual order of operations on point $P$ (before inversion) would be:
1. $W_Y \cdot P$: Convert from Z-up to Y-up world coordinates
2. $E \cdot (W_Y \cdot P)$: Apply camera transform
3. $C_{GL} \cdot (E \cdot (W_Y \cdot P))$: Convert to OpenGL camera convention
In our code:
```typescript
finalTransform
.multiply(cameraConversion) // C_GL
.multiply(worldToCamera) // E
.multiply(worldZupToYup) // W_Y
```
Three.js's `multiply()` method actually applies the matrices in the opposite order of how we write them (it pre-multiplies), which is why our code worked. But my explanation of the order was incorrect.
The actual sequence is:
1. $C_{GL}$: Convert camera space to OpenGL convention
2. $E$: Transform from world to camera
3. $W_Y$: Convert world from Z-up to Y-up
Thank you for catching this! It's crucial to understand the correct order of operations, especially when dealing with coordinate system transformations.
Let me break this down more clearly:
## Why do we need these conversions?
1. **World Coordinate Conversion (Z-up to Y-up)**:
- Your original data uses a Z-up coordinate system (Z is "up" in the real world)
- Three.js uses a Y-up coordinate system (Y is "up" in Three.js)
- We need to convert between these two world coordinate systems
2. **Camera Coordinate Conversion (OpenCV to OpenGL)**:
- Camera coordinate systems define how the camera "sees" the world
- OpenCV uses a right-handed system where Z points forward from the camera
- OpenGL/Three.js uses a system where Z points backward from the camera
- We need to convert between these camera conventions
## What's the difference?
- **World conversion** (Z-up to Y-up) affects how we represent points in the world
- **Camera conversion** (OpenCV to OpenGL) affects how the camera interprets what it sees
These are completely separate transforms that operate on different coordinate spaces:
- One operates on the world before the camera sees it
- The other operates on the camera's view of the world
## Why this specific order?
The order matters because we're dealing with a transformation pipeline:
1. First, we want to convert the world from Z-up to Y-up (`worldCvt`)
- This makes the world match Three.js's expectations
2. Then, apply the camera's extrinsic matrix (`Rt`)
- This transforms world points into the camera's coordinate system
- At this point, we're in OpenCV camera space
3. Finally, convert from OpenCV to OpenGL camera conventions (`cameraCvt`)
- This converts the camera space to match Three.js's expectations
If we changed the order:
- Applying camera conversion before the camera transformation would incorrectly transform world points
- Applying world conversion after the camera transformation would transform points that are already in camera space
Think of it as a pipeline:
1. Fix the world coordinates (world conversion)
2. View the world through the camera (camera extrinsic)
3. Adjust how the camera interprets what it sees (camera conversion)
The fact that matrix multiplication is associative means we can compute this entire pipeline as a single matrix operation, but the conceptual order still matters for getting the correct result.
---
[Matrix4.multiply](https://threejs.org/docs/#api/en/math/Matrix4.multiply)

View File

@ -1,10 +1,29 @@
import { Grid, useBVH, useGLTF, CameraControls, AccumulativeShadows, OrbitControls, Stats } from '@react-three/drei'
import { Camera, Canvas, useFrame, useThree, useLoader } from '@react-three/fiber'
import { Camera, Canvas, useFrame, useThree, useLoader, RenderCallback, RootState } from '@react-three/fiber'
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 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"
// 133, 3
type PosePoints3D = [number, number, number][]
// F, 133, 3
type AnimePosePoints3D = PosePoints3D[]
interface Skeleton0402 {
"a": PosePoints3D
"b": PosePoints3D
}
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 THREE_ADDONS = {
FontLoader,
@ -21,52 +40,113 @@ const CV_TO_GL_MAT = new THREE.Matrix4().set(
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[]> = {
"AE_01": [
0.2321047, -0.97264263, -0.0096808, -0.96323585, 0.06882254,
0.02634936, -0.99728089, 0.03661007, 0.97025299, 0.23080732,
0.07305554, 3.34933242, 0., 0., 0.,
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.
],
] as const,
"AE_1A": [
0.93194049, -0.35571886, -0.07036343, -0.92123075, 0.01084819,
0.22131041, -0.97514308, 0.24922173, 0.36244895, 0.908012,
0.21010704, 4.87284891, 0., 0., 0.,
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.
],
] as const,
"AE_08": [
0.66806102, -0.74355508, -0.02864123, -1.10173496, 0.05931037,
0.09157787, -0.99403007, 0.26760438, 0.74173901, 0.66237402,
0.10528013, 6.92372493, 0., 0., 0.,
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
] 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.
],
] as const,
"AE_1A": [
3467.39715751, 0., 1000.62548655, 0.,
3473.7168112, 831.64048503, 0., 0.,
1.
],
] as const,
"AE_08": [
2785.43931794, 0., 1254.98272372, 0.,
2788.10437965, 738.82985324, 0., 0.,
1.
]
} as const
] as const
}
const IMAGE_WIDTH = 2560
const IMAGE_HEIGHT = 1440
@ -111,12 +191,14 @@ const Scene = () => {
let Rt: THREE.Matrix4
if (extrinsic instanceof THREE.Matrix4) {
Rt = extrinsic
} else {
} 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")
}
console.assert(Rt.elements.length === 16, "extrinsic must be a 4x4 matrix")
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]} />
@ -153,36 +235,221 @@ const Scene = () => {
)
}
const preProcessExtrinsic = (extrinsic: number[]) => {
const Rt = new THREE.Matrix4()
// @ts-expect-error 16 elements
Rt.set(...extrinsic)
Rt.invert()
Rt.multiply(CV_TO_GL_MAT)
return Rt
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
}
const Human3DSkeleton = ({
skeleton,
startFrame = 0,
jointRadius = 0.01,
boneRadius = 0.005,
showJoints = true,
showBones = true,
frameRate = 30
}: Human3DSkeletonProps) => {
const [frameIndex, setFrameIndex] = useState(startFrame)
const totalFrames = skeleton.length
const onFrame: RenderCallback = (totalFrames === 0) ? (state, delta) => { } : (state: RootState, delta: number) => {
// Calculate next frame based on desired frame rate and delta time
setFrameIndex(prevFrame => {
// Calculate next frame
const nextFrame = prevFrame + frameRate * delta
// Loop back to start if we reach the end
return nextFrame >= totalFrames ? 0 : nextFrame
})
return null
}
// Use frame to animate through the skeleton poses
useFrame(onFrame)
// Get the current frame joints - use Math.floor to get the nearest frame
const currentFrame = Math.floor(frameIndex) % totalFrames
const joints = skeleton[currentFrame]
// Function to get appropriate color for a joint index
const getJointColor = (idx: number) => {
// Face joints (23-90)
if (idx >= 23 && idx <= 90) return COLOR_FACE
// Hand joints (91-132)
if (idx >= 91 && idx <= 132) return COLOR_FINGERS
// Foot joints (17-22)
if (idx >= 17 && idx <= 22) return COLOR_FOOT
// Head (0-4)
if (idx <= 4) return COLOR_HEAD
// Arms (5-10)
if (idx >= 5 && idx <= 10) return COLOR_ARMS
// Legs (11-16)
if (idx >= 11 && idx <= 16) return COLOR_LEGS
// Default
return COLOR_SPINE
}
// 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
}
// Create the joint spheres
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
// Create the bone cylinders
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]
// Calculate midpoint and length
const midpoint = new THREE.Vector3().addVectors(startPos, endPos).multiplyScalar(0.5)
const length = startPos.distanceTo(endPos)
// Calculate rotation
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])
// Calculate midpoint and length
const midpoint = new THREE.Vector3().addVectors(startPos, endPos).multiplyScalar(0.5)
const length = startPos.distanceTo(endPos)
// Calculate rotation
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 S0 = [POSE_3D_MANY[0][0]]
// const S1 = [POSE_3D_MANY[0][1]]
// const skeletons = POSE_3D_MANY.map((el) => <Human3DSkeleton jointRadius={0.005} boneRadius={0.0025} frameRate={24} skeleton={el} />)
// const skeletons = [<Human3DSkeleton jointRadius={0.05} boneRadius={0.025} frameRate={1} skeleton={S0} />,
// <Human3DSkeleton jointRadius={0.05} boneRadius={0.025} frameRate={1} skeleton={S1} />
// ]
// const skeletons = [
// <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]} />,
]
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 /> */}
{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} />
})}
<CameraViewFromExtrinsic name="default" extrinsic={DEFAULT_TRANSFORMATION_MATRIX} fov={60} aspect={IMAGE_WIDTH / IMAGE_HEIGHT} far={0.4} />
{ }
<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 />