Three.js dot product ( feat. quaternion )

강정우·2025년 1월 23일
0

three.js

목록 보기
24/24

Get Dice Value

어떻게 하면 주사위 값을 가져올 수 있을까 굉장히 고민을 많이 하였다.

우선 우리는 모든 Object3D 에는 정점 데이터와 이를 빠르게 계산하여 위치를 표출하기 위한 matrix 데이터가 있다는 것을 알고있다.

그리고 이를 활용하여 두 점곱의 유사율을 알 수 있는데 이를 활용하여 어떤 면이 현재 위쪽면인지 알 수 있다.
이제 차근차근 알아보자.

1. 점곱 ( dot product )

점곱은 두 벡터 간의 곱셈 연산으로, 영어로는 "dot product" 라고 한다.

점곱은 두 벡터의 크기와 방향을 고려하여 계산된 스칼라 값을 반환한다.
두 벡터 a=(ax, ay, ax) 와 b=(bx, by, bz) 의 점곱은 다음과 같이 정의된다.

a*b = ax*bx + ay*by + az*bz

따라서 이 점곱이 최대값을 가지면 같은 방향을, 최소값을 가지면 반대방향을 나타낸다.
그리고 우리는 이를 활용하여 각 면들에 값을 지정하여 각 면들과 현재 Dice 의 쿼터니안을 갖고 각 면을계산하여 어떤 면이 위를 향하는지 확인하고 최종적으로 값을 표출해 줄 것이다.

그럼 여기에 필요한 개념들을 추가적으로 알아보자.

2. 개념

앞서 자동차 만들기에서 아주 짧게 나왔었는데 quaternion 은 오일러의 Gimbol lock 을 해소하기위해 나타난 개념으로 Mesh 가 회전한 값을 갖는 속성값이다.

그리고 우리가 설정한 각 면에 객체의 회전 상태를 계산한 결과와 Wolrd 에서 윗면을 갖는 Vercotr3(0,1,0) 과 비교하여 유사성을 따져 어떤 면이 윗면을 향하는 지를 알아낸다면 주사위의 어떤 면이 위를 향하고 있는지 알 수 있다.

그리고 다행이 Three.js 에서는 dot() 함수를 제공해준다.

2. dot()

dot() 은 두 벡터 간의 점곱을 계산한다.

앞서 말한 두 방향 벡터의 점곱을 계산해준다.
여기서 각 면에 quaternion 을 계산한 값과 위를 향하는 방향 벡터 new THREE.Vector3(0, 1, 0) 간의 점곱을 계산함으로써, 주사위의 특정 면이 얼마나 위쪽을 향하고 있는지를 수치적으로 알 수 있다.

양수일 경우 - 주사위의 면이 위쪽을 향하고 있음
0일 경우 - 주사위의 면이 수평이거나 수직이 아님
음수일 경우 - 주사위의 면이 아래쪽을 향하고 있음

그럼 이제 이를 바탕으로 코드를 작성해보자.

3. code

type FaceDirections = "+Y" | "-Y" | "+Z" | "-Z" | "+X" | "-X";
type FaceValues = Record<string, Record<FaceDirections, number>>;

interface UpDirections {
    [key: string]: THREE.Vector3;
}

const faceValues = {
    "+Y": 1,
    "-Y": 6,
    "+Z": 2,
    "-Z": 5,
    "+X": 3,
    "-X": 4
};

const upDirections: UpDirections = {
        "+Y": new THREE.Vector3(0, 1, 0), // 1
        "-Y": new THREE.Vector3(0, -1, 0), // 6
        "+Z": new THREE.Vector3(0, 0, 1), // 2
        "-Z": new THREE.Vector3(0, 0, -1), // 5
        "+X": new THREE.Vector3(1, 0, 0), // 3
        "-X": new THREE.Vector3(-1, 0, 0), // 4
    };

getDiceValue(): number {
    const currentQuaternion = this.mesh.quaternion.clone();
    let closestFace: FaceDirections | null = null;
    let maxDot = -Infinity;

    for (const [face, direction] of Object.entries(upDirections)) {
        const transformedDirection = direction.clone().applyQuaternion(currentQuaternion);
        const dot = transformedDirection.dot(new THREE.Vector3(0, 1, 0));
        if (dot > maxDot) {
            maxDot = dot;
            closestFace = face as FaceDirections;
        }
    }

    return closestFace ? this.faceValues[this.mesh.uuid][closestFace] : 100;
}

우선 타입과 인터페이스를 선언 한다.

currentQuaternion : 현재 클릭한 주사위의 회전(quaternion) 속성 값 저장.
maxDot : 최대값을 저장하기 위한 최소값으로 초기화.
for() : 선언한 6개의 면에 대해 각각 currentQuaternion 을 적용하기위한 반복문
direction.clone() : 원본 데이터를 해치면 안 되니 클론 후 해당 값에 적용
transformedDirection : 클론 한 데이터를 적용 후 저장
new THREE.Vector3(0, 1, 0) : World 의 윗면과 비교.
.dot() : 두 벡터의 방향과 크기의 유사성을 비교
dot > maxDot : 계속 비교하여 가장 윗면을 표현하는 값을 저장
return : null 이 아닐 때 최종적으로 값을 선정.

profile
智(지)! 德(덕)! 體(체)!

0개의 댓글

관련 채용 정보