고정 축을 중심으로 개체 회전


12

내 앱 사용자가 화면에서 손가락을 드래그하여 화면 중앙에 그려진 3D 객체를 회전 시키려고합니다. 화면에서 수평 이동은 고정 된 Y 축을 중심으로 회전하는 것을 의미하고 수직 이동은 X 축을 중심으로 회전하는 것을 의미합니다. 내가 겪고있는 문제는 한 축을 중심으로 회전을 허용하면 객체가 잘 회전하지만 두 번째 회전을 시작하자마자 예상대로 객체가 회전하지 않는다는 것입니다.

다음은 무슨 일이 일어나고 있는지 보여주는 그림입니다.

여기에 이미지 설명을 입력하십시오

파란색 축은 고정 축을 나타냅니다. 이 고정 된 파란색 축이있는 화면을 그리십시오. 이것이 객체가 회전하기를 원하는 것입니다. 일어나는 일은 빨간색입니다.

내가 아는 것은 다음과 같습니다.

  1. Y (0, 1, 0) 주위의 첫 번째 회전으로 인해 모델이 파란색 공간 (이 공간 A라고 함)에서 다른 공간 (이 공간 B라고 함)으로 이동합니다.
  2. 벡터 (1, 0, 0)를 사용하여 다시 회전하려고하면 공간 A의 공간이 아닌 공간 B의 x 축을 중심으로 회전합니다.

내가 아는 것을 감안할 때 내가 시도한 것은 다음과 같습니다 (간결하게 W 코드를 생략).

  1. 먼저 쿼터니언을 사용하여 Y (0, 1, 0) 주위를 회전합니다.
  2. 회전 Y 쿼터니언을 행렬로 변환합니다.
  3. Y 회전 행렬에 고정 축 x 벡터 (1, 0, 0)를 곱하여 새로운 공간과 관련하여 X 축을 얻습니다.
  4. 쿼터니언을 사용하여이 새로운 X 벡터를 중심으로 회전합니다.

코드는 다음과 같습니다.

private float[] rotationMatrix() {

    final float[] xAxis = {1f, 0f, 0f, 1f};
    final float[] yAxis = {0f, 1f, 0f, 1f};
    float[] rotationY = Quaternion.fromAxisAngle(yAxis, -angleX).toMatrix();

    // multiply x axis by rotationY to put it in object space
    float[] xAxisObjectSpace = new float[4];
    multiplyMV(xAxisObjectSpace, 0, rotationY, 0, xAxis, 0);

    float[] rotationX = Quaternion.fromAxisAngle(xAxisObjectSpace, -angleY).toMatrix();

    float[] rotationMatrix = new float[16];
    multiplyMM(rotationMatrix, 0, rotationY, 0, rotationX, 0);
    return rotationMatrix;
  }

이것은 내가 기대하는 방식으로 작동하지 않습니다. 회전이 작동하는 것처럼 보이지만 어떤 시점에서는 수평 이동이 Y 축을 중심으로 회전하지 않고 Z 축을 중심으로 회전하는 것처럼 보입니다.

내 이해가 잘못되었는지 또는 다른 것이 문제를 일으키는 지 확실하지 않습니다. 회전 외에도 객체에 다른 변형이 있습니다. 회전을 적용하기 전에 개체를 중앙으로 이동합니다. 위의 함수에서 반환 된 행렬을 사용하여 회전 한 다음 객체를 볼 수 있도록 Z 방향으로 -2로 변환합니다. 나는 이것이 내 회전을 망칠 것이라고 생각하지 않지만 어쨌든 그 코드는 다음과 같습니다.

private float[] getMvpMatrix() {
    // translates the object to where we can see it
    final float[] translationMatrix = new float[16];
    setIdentityM(translationMatrix, 0);
    translateM(translationMatrix, 0, translationMatrix, 0, 0f, 0f, -2);

    float[] rotationMatrix = rotationMatrix();

    // centers the object
    final float[] centeringMatrix = new float[16];
    setIdentityM(centeringMatrix, 0);
    float moveX = (extents.max[0] + extents.min[0]) / 2f;
    float moveY = (extents.max[1] + extents.min[1]) / 2f;
    float moveZ = (extents.max[2] + extents.min[2]) / 2f;
    translateM(centeringMatrix, 0, //
      -moveX, //
      -moveY, //
      -moveZ //
    );

    // apply the translations/rotations
    final float[] modelMatrix = new float[16];
    multiplyMM(modelMatrix, 0, translationMatrix, 0, rotationMatrix, 0);
    multiplyMM(modelMatrix, 0, modelMatrix, 0, centeringMatrix, 0);

    final float[] mvpMatrix = new float[16];
    multiplyMM(mvpMatrix, 0, projectionMatrix, 0, modelMatrix, 0);
    return mvpMatrix;
  }

나는 며칠 동안 이것에 붙어있다. 도움을 주셔서 감사합니다.

===================================================== ================

최신 정보:

Unity에서 작동하도록하는 것은 간단합니다. 다음은 원점을 중심으로 큐브를 회전시키는 코드입니다.

public class CubeController : MonoBehaviour {

    Vector3 xAxis = new Vector3 (1f, 0f, 0f);
    Vector3 yAxis = new Vector3 (0f, 1f, 0f);

    // Update is called once per frame
    void FixedUpdate () {
        float horizontal = Input.GetAxis ("Horizontal");
        float vertical = Input.GetAxis ("Vertical");

        transform.Rotate (xAxis, vertical, Space.World);
        transform.Rotate (yAxis, -horizontal, Space.World);
    }
}

내가 예상 한대로 회전을 작동시키는 부분 은 변환 함수 의 Space.World매개 변수 Rotate입니다.

Unity를 사용할 수 있다면 불행히도이 동작을 직접 코딩해야합니다.


1
내 대답은 여기 gamedev.stackexchange.com/questions/67199/… 도움이 될 것입니다 ..
concept3d

나는 당신의 대답 뒤에있는 개념을 이해하지만 구현 방법은 나를 피합니다.
크리스토퍼 페리

다른 답변을 확인하면 syntac answer가 내가 설명한 아이디어를 구현합니다.
concept3d

아니, 다른 축에 대해 여러 번 회전하고 있습니다. 단일 회전을하는 것이 좋습니다.
Christopher Perry

답변:


3

당신이 가진 문제는 gimble lock 입니다. 나는 당신이 찾고있는 것을 arcball rotation 이라고 생각합니다 . arcball에 대한 수학은 약간 복잡 할 수 있습니다.

더 간단한 방법은 화면에서 2d 스 와이프에 수직 인 2d 벡터를 찾는 것입니다.

벡터를 가지고 비행기 근처의 카메라에 투영하여 월드 공간에서 3D 벡터를 얻습니다. 월드 공간에 화면 공간 .

그런 다음이 벡터로 쿼터니언을 만들어 gameobject에 곱하십시오. 아마도 약간의 슬러 프 또는 레프 전환이있을 수 있습니다.

편집하다:

Unity 예제 : 단일 예제에서 게임 오브젝트 회전의 내부 상태는 행렬이 아닌 쿼터니언입니다. transform.rotation 메서드는 제공된 벡터와 각도를 기반으로 쿼터니언을 생성하고 해당 쿼터니언에 게임 오브젝트 회전 쿼터니언을 곱합니다. 나중에 렌더링 또는 물리에 대한 회전 행렬 만 생성합니다. 쿼터니언은 부가 적이며 mble 블 잠금을 피합니다.

코드는 다음과 같아야합니다.

private float[] rotationMatrix() {

    final float[] xAxis = {1f, 0f, 0f, 1f};
    final float[] yAxis = {0f, 1f, 0f, 1f};

    Quaternion qY = Quaternion.fromAxisAngle(yAxis, angleX);
    Quaternion qX = Quaternion.fromAxisAngle(xAxis, -angleY);

    return (qX * qY).getMatrix(); // should probably represent the gameobjects rotation as a quaternion(not a matrix) and multiply all 3 quaternions before generating the matrix. 
  }

ArcBall Rotation Opengl 튜토리얼


짐벌 잠금을 얻지 못했습니다. 첫 번째 회전은 축을 이동하므로 두 번째 회전은 이동 된 축을 기준으로합니다. 내가 제공 한 사진을 다시 한번보십시오.
크리스토퍼 페리

나는 당신이 그것을 이해하기를 바랍니다. 한마디로. 쿼터니언을 곱하여 회전을 적용 할 수 있습니다. 모든 회전 계산이 끝날 때만 행렬을 생성해야합니다. 또한 xQ * yQ는 yQ * xQ와 같지 않습니다. 크리스토퍼 페리 (Christopher Perry)가 말한 것처럼 쿼터니언은 비 정기적입니다.
Anthony Raimondo

모든 코드를 여기에 넣습니다 . 나는 모든 것을 시도한 것처럼 느낍니다. 어쩌면 다른 사람의 눈이 내 실수를 잡을 수도 있습니다.
크리스토퍼 페리

나는 그것을 받아들이지 않았다. 스택 교환 알고리즘은 자동으로 포인트를 할당했다. : /
Christopher Perry

이 불의에 대해 죄송합니다.
Anthony Raimondo

3

누적 된 회전 행렬을 회전하여 예상되는 회전을 얻을 수있었습니다.

setIdentityM(currentRotation, 0);
rotateM(currentRotation, 0, angleY, 0, 1, 0);
rotateM(currentRotation, 0, angleX, 1, 0, 0);

// Multiply the current rotation by the accumulated rotation,
// and then set the accumulated rotation to the result.
multiplyMM(temporaryMatrix, 0, currentRotation, 0, accumulatedRotation, 0);
arraycopy(temporaryMatrix, 0, accumulatedRotation, 0, 16);

1

이미지가 rotationMatrix 코드와 일치합니다. 이전 y 회전으로 x 축을 회전하면 로컬 x 축이 표시되고 객체를 회전하면 이미지에 표시되는 결과가 나타납니다. 사용자 관점에서 회전을 논리적으로하려면 월드 좌표 축을 사용하여 객체를 회전하려고합니다.

사용자가 객체를 여러 번 회전 할 수있게하려면 회전 대신 행렬 대신 쿼터니언으로 회전을 저장하는 것이 합리적입니다. 회전 행렬은 물론 쿼터니언에서도 동일하지만 쿼터니언을 정규화하면 다시 좋은 회전으로 돌아옵니다.

아이디 쿼터니언을 초기 값으로 사용하기 만하면 사용자가 화면을 쓸어 넘길 때마다 Quaternion.fromAxisAngle(yAxis, -angleX)코드로 쿼터니언을 회전시킵니다 . x 회전에는 항상 (1,0,0,1)을 사용하고 y 회전에는 (0,1,0,1)을 사용하십시오.

static final float[] xAxis = {1f, 0f, 0f, 1f};
static final float[] yAxis = {0f, 1f, 0f, 1f};

private void rotateObject(float angleX, float angleY) {
  Quaternion rotationY = Quaternion.fromAxisAngle(yAxis, -angleX);
  Quaternion rotationX = Quaternion.fromAxisAngle(xAxis, -angleY);

  myRotation = myRotation.rotate(rotationY).rotate(rotationX).normalize();
}
private float[] rotationMatrix() {
  return myRotation.toMatrix();
}

언어 나 특정 프레임 워크를 언급하지 않았기 때문에 Quaternion의 메소드는 물론 다르게 호출 될 수 있으며, 정규화는 자주 호출 할 필요는 없지만 화면을 스 와이프하면 회전이 발생하므로 속도를 늦추면 쿼터니언이 쿼터니언에서 떨어질 가능성이 없습니다.


이 작업을 수행하는 것과 똑같은 동작이 발생합니다.
크리스토퍼 페리

1
@ChristopherPerry 회전 순서를 반대로 해보십시오. 순환 myRotation = rotationX.rotate(rotationY).rotate(myRotation).normalize()되지 않으므로 수행 순서가 중요합니다. 그래도 작동하지 않으면 프레임 워크 / 언어에 다른 의견을 추가하고 조금 더 자세히 살펴 보겠습니다.
Daniel Carlsson

그것은 작동하지 않습니다, 나는 같은 행동을 얻습니다.
크리스토퍼 페리

나는 내 코드를 모두 여기에
Christopher Perry
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.