어떻게 하면 주사위 값을 가져올 수 있을까 굉장히 고민을 많이 하였다.
우선 우리는 모든 Object3D 에는 정점 데이터와 이를 빠르게 계산하여 위치를 표출하기 위한 matrix 데이터가 있다는 것을 알고있다.
그리고 이를 활용하여 두 점곱의 유사율을 알 수 있는데 이를 활용하여 어떤 면이 현재 위쪽면인지 알 수 있다.
이제 차근차근 알아보자.
점곱은 두 벡터 간의 곱셈 연산으로, 영어로는 "dot product" 라고 한다.
점곱은 두 벡터의 크기와 방향을 고려하여 계산된 스칼라 값을 반환한다.
두 벡터 a=(ax, ay, ax) 와 b=(bx, by, bz) 의 점곱은 다음과 같이 정의된다.
a*b = ax*bx + ay*by + az*bz
따라서 이 점곱이 최대값을 가지면 같은 방향을, 최소값을 가지면 반대방향을 나타낸다.
그리고 우리는 이를 활용하여 각 면들에 값을 지정하여 각 면들과 현재 Dice 의 쿼터니안을 갖고 각 면을계산하여 어떤 면이 위를 향하는지 확인하고 최종적으로 값을 표출해 줄 것이다.
그럼 여기에 필요한 개념들을 추가적으로 알아보자.
앞서 자동차 만들기에서 아주 짧게 나왔었는데 quaternion
은 오일러의 Gimbol lock 을 해소하기위해 나타난 개념으로 Mesh 가 회전한 값을 갖는 속성값이다.
그리고 우리가 설정한 각 면에 객체의 회전 상태를 계산한 결과와 Wolrd 에서 윗면을 갖는 Vercotr3(0,1,0)
과 비교하여 유사성을 따져 어떤 면이 윗면을 향하는 지를 알아낸다면 주사위의 어떤 면이 위를 향하고 있는지 알 수 있다.
그리고 다행이 Three.js 에서는 dot()
함수를 제공해준다.
dot()
은 두 벡터 간의 점곱을 계산한다.
앞서 말한 두 방향 벡터의 점곱을 계산해준다.
여기서 각 면에 quaternion 을 계산한 값과 위를 향하는 방향 벡터 new THREE.Vector3(0, 1, 0)
간의 점곱을 계산함으로써, 주사위의 특정 면이 얼마나 위쪽을 향하고 있는지를 수치적으로 알 수 있다.
양수일 경우 - 주사위의 면이 위쪽을 향하고 있음
0일 경우 - 주사위의 면이 수평이거나 수직이 아님
음수일 경우 - 주사위의 면이 아래쪽을 향하고 있음
그럼 이제 이를 바탕으로 코드를 작성해보자.
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 이 아닐 때 최종적으로 값을 선정.