코딩하는 고릴라

[FE] React-three/fiber로 하늘에 별 띄우기 본문

Project/별일

[FE] React-three/fiber로 하늘에 별 띄우기

코릴라입니다 2024. 3. 5. 01:25
반응형

🦍 상황

- React-three/fiber를 활용하여 별이 떠있는 밤하늘을 3D로 구현해야했다.
- 별들의 x, y, z 좌표를 계산해 하늘에 띄워야 했고, 별들을 잇는 선분들의 좌표 또한 필요로 했다.


🐈 로직

1. 별 띄우기

1. 별자리 이미지를 구해온다 (스텔라리움)
2. 블렌더에서 해당 별자리 이미지를 배경으로 설정하고 별들이 위치한 곳에 구체(Sphere)를 위치시켜 X, Z 좌표를 구한다.

3. 별들의 높이(Y 좌표)는 피타고라스 정리를 통하여 구한다.
4. 모든 별들의 X, Y, Z 좌표를 하드코딩하여 배열로 저장

# data.js

// position : 별 [x, y, z]
export const position = [
    // 0 URSA MINOR 작은곰자리
    [
        [0, 35, 0],
        [-1.8, 35, -0.5],
        [-3.9, 34.8, -1.5],
        [-5.1, 34.6, -3.6],
        [-6.5, 34.5, -3.4],
        [-7.0, 34.1, -6.1],
        [-5.4, 34.3, -6.0],
    ],
    // 1 CEPHEUS 케페우스자리
    [
        [-0.9, 34.7, 5.7],
        [-6.2, 34.1, 7.3],
        [-4.0, 33.7, 10.8],
        [-7.7, 32.5, 13.7],
        [-9.2, 33.1, 9.9],
    ],
    // //2 CASIOPEIA 카시오페이아 자리
    [
        [5.8, 33.3, 11.4],
        [5.0, 32.8, 13.7],
        [3.0, 32.6, 14.9],
        [2.3, 32.2, 16.3],
        [0.0, 32.7, 15.1],
    ],
    
    // ...
]

 
5. 해당 좌표 값을 import 한 후, Star 컴포넌트 내 mesh의 position 속성에 해당 x, y, z 좌표 설정

# UserSpace.js

function Star(props) {

	// ...

    return (
        <>
            <mesh ref={mesh} position={props.position}> {/* mesh의 position 속성에 x, y, z 좌표 설정 */}
                <sphereGeometry args={props.size} /> 
                <meshPhongMaterial
                    color={curStarState ? colors[colorCheck] : "grey"}
                    opacity={
                        curStarState ? Math.max(1 - dayDiff / 360, 0.5) : 0.4
                    }
                    transparent={true}
                />
            </mesh>
            <StarSurround
                position={props.position}
                location={props.location}
                handleClick={handleClick}
            />
        </>
    );
}

 
 

2. 별자리 선 만들기

1. 각 별들과 별 사이를 잇기 위해 별들의 좌표를 이용해 별자리 선의 형태를 하드코딩
 - 이 때, 선을 한붓그리기 형태로 쭉 잇게 된다면 원하는 별자리의 형태가 나타나지 않아 3차원 배열의 구조를 가지게끔 한 후, 쭉 이어지는 선들을 하나의 인덱스 내에 저장

# data.js

export const linePosition = [
    // 0 URSA MINOR 작은곰자리
    [
        [
            [0, 35, 0],
            [-1.8, 35, -0.5],
            [-3.9, 34.8, -1.5],
            [-5.1, 34.6, -3.6],
            [-6.5, 34.5, -3.4],
            [-7.0, 34.1, -6.1],
            [-5.4, 34.3, -6.0],
        ],
        [
            [-5.1, 34.6, -3.6],
            [-5.4, 34.3, -6.0],
        ],
    ],
    //1 CEPHEUS 케페우스자리
    [
        [
            [-0.9, 34.7, 5.7],
            [-6.2, 34.1, 7.3],
            [-4.0, 33.7, 10.8],
            [-7.7, 32.5, 13.7],
            [-9.2, 33.1, 9.9],
            [-6.2, 34.1, 7.3],
        ],
        [
            [-0.9, 34.7, 5.7],
            [-4.0, 33.7, 10.8],
        ],
    ],
    // //2 CASIOPEIA 카시오페이아 자리
    [
        [
            [5.8, 33.3, 11.4],
            [5.0, 32.8, 13.7],
            [3.0, 32.6, 14.9],
            [2.3, 32.2, 16.3],
            [0.0, 32.7, 15.1],
        ],
    ],
    //3 CAMELODALIS 기린 자리
    [
        [
            [5.1, 34.7, 0.7],
            [7.6, 34.2, 4.8],
            [11.0, 33.7, 3.4],
            [11.2, 33.3, 6.8],
            [11.6, 32.7, 9.3],
        ],
        [
            [7.6, 34.2, 4.8],
            [11.6, 32.7, 9.3],
        ],
    ],
    
    // ...
]

 
2. 해당 좌표 값을 import 한 후, Line 컴포넌트의 상위 컴포넌트인 GroupStar 컴포넌트에서 해당 좌표를 THREE.Vector3 값으로 변경 후 props를 통해 Line 컴포넌트로 전달

function GroupStar(props) {

    // ...
    
    return (
        <group
            ref={group}
            onPointerEnter={handlePointerEnter}
            onPointerLeave={handlePointerLeave}
        >
        
            // ...
            
            {/* 하나의 별자리 선을 THREE.Vector3 형태로 바꿔준 후 pos에 저장 */}
            {linePosition[groupNum].map((it, index) => {
                const pos = it.map((it) => new THREE.Vector3(...it));
                return (
                    <Line
                        key={index}
                        points={pos} {/* 여러개의 Point의 좌표를 가진 pos를 props로 전달 */}
                        groupNum={groupNum}
                        lineColor={lineColor}
                    />
                );
            })}
        </group>
    );
}

 
3. Line 컴포넌트에서 props로 전달받은 점들의 좌표값을 이용해 geometry 생성 및 line 생성

function Line(props) {
	
    // ...
    
    {/* 전달받은 점들의 좌표를 활용하여 LineGeometry 생성 */}
    const lineGeometry = new THREE.BufferGeometry().setFromPoints(props.points);

    return (
        <line geometry={lineGeometry}> {/* 위에서 생성한 lineGeometry 활용하여 line 생성 */}
            <lineBasicMaterial
                attach="material"
                ref={lineRef}
                transparent={true}
                opacity={
                    !props.lineColor
                        ? 1
                        : starLineOpacity === groupNum
                        ? 0.05
                        : 0.01
                }
                color={0xced6ff}
            />
        </line>
    );
}

🐄 결과

- 3D 공간에 별과 별을 잇는 선을 생성할 수 있게 되었다.


🐇 고찰

- 좌표값 설정을 위해 하드코딩 하는데 시간이 다소 소요됐다. 더 좋은 방법이 있을까 고민했었으나 이런 방식으로 하드코딩하는게 최선이었다고 생각한다.

반응형