Mengorientasikan model untuk menghadapi target

28

Saya memiliki dua objek (target dan pemain), keduanya memiliki Posisi (Vector3) dan Rotasi (Quaternion). Saya ingin target berputar dan menghadap tepat ke pemain. Targetnya, ketika menembak sesuatu harus menembak tepat ke pemain.

Saya telah melihat banyak contoh slerping kepada pemain, tapi saya tidak ingin rotasi tambahan, well, saya kira itu akan baik-baik saja selama saya bisa membuat slerp menjadi 100%, dan selama itu benar-benar berfungsi.

FYI - Saya dapat menggunakan posisi dan rotasi untuk melakukan banyak hal lain dan semuanya bekerja dengan baik kecuali bagian terakhir ini yang tidak dapat saya pahami.

Contoh kode dijalankan di kelas Target, Posisi = posisi target, Avatar = pemain.

EDIT

Saya sekarang menggunakan kode c # Maik yang telah dia sediakan dan bekerja dengan sangat baik!

Marc
sumber
4
Jika Anda melakukan slerp 100%, Anda tidak menggunakan slerp. Anda hanya mengatur rotasi ke 0*(rotation A) + 1*(rotation B)- dengan kata lain, Anda hanya mengatur rotasi ke rotasi B. Slerp hanya untuk menentukan seperti apa rotasi itu (0% <x <100%) dari jalan di antaranya.
doppelgreener
Ok, masuk akal, tapi, target masih belum berputar sepenuhnya ke arah pemain ... "jauh" dengan kode itu.
Marc

Jawaban:

20

Ada lebih dari satu cara untuk melakukannya. Anda dapat menghitung orientasi absolut atau rotasi relatif terhadap avatar Anda, itu berarti orientasi baru Anda = avatarOrientation * q. Inilah yang terakhir:

  1. Hitung sumbu rotasi dengan mengambil produk silang dari vektor unit forward avatar Anda dan vektor unit dari avatar ke target, vektor forward baru:

    vector newForwardUnit = vector::normalize(target - avatarPosition);
    vector rotAxis = vector::cross(avatarForwardUnit, newForwardUnit);
    
  2. Hitung sudut rotasi menggunakan produk-titik

    float rotAngle = acos(vector::dot(avatarForwardUnit, newForwardUnit));
  3. Buat angka empat menggunakan rotAxis dan rotAngle dan gandakan dengan orientasi avatar saat ini

    quaternion q(rotAxis, rotAngle);
    quaternion newRot = avatarRot * q;
    

Jika Anda perlu bantuan menemukan vektor maju avatar saat ini, input untuk 1. tembak saja :)

EDIT: Menghitung orientasi absolut sebenarnya sedikit lebih mudah, gunakan vektor forward dari matriks identitas daripada vektor forward avatar sebagai input untuk 1) dan 2). Dan jangan gandakan dalam 3), alih-alih gunakan langsung sebagai orientasi baru:newRot = q


Penting untuk dicatat: Solusi ini memiliki 2 anomali yang disebabkan oleh sifat dari produk-silang:

  1. Jika vektor maju sama. Solusi di sini adalah hanya mengembalikan angka empat identitas

  2. Jika titik vektor tepat di arah yang berlawanan. Solusi di sini adalah membuat angka empat dengan menggunakan sumbu avatar sebagai sumbu rotasi dan sudut 180,0 derajat.

Berikut ini adalah implementasi dalam C ++ yang menangani kasus tepi tersebut. Mengubahnya ke C # seharusnya mudah.

// returns a quaternion that rotates vector a to vector b
quaternion get_rotation(const vector &a, const vector &b, const vector &up)
{   
    ASSERT_VECTOR_NORMALIZED(a);
    ASSERT_VECTOR_NORMALIZED(b);

    float dot = vector::dot(a, b);    
    // test for dot -1
    if(nearly_equal_eps_f(dot, -1.0f, 0.000001f))
    {
        // vector a and b point exactly in the opposite direction, 
        // so it is a 180 degrees turn around the up-axis
        return quaternion(up, gdeg2rad(180.0f));
    }
    // test for dot 1
    else if(nearly_equal_eps_f(dot, 1.0f, 0.000001f))
    {
        // vector a and b point exactly in the same direction
        // so we return the identity quaternion
        return quaternion(0.0f, 0.0f, 0.0f, 1.0f);
    }

    float rotAngle = acos(dot);
    vector rotAxis = vector::cross(a, b);
    rotAxis = vector::normalize(rotAxis);
    return quaternion(rotAxis, rotAngle);
}

EDIT: Versi diperbaiki dari kode XNA Marc

// the new forward vector, so the avatar faces the target
Vector3 newForward = Vector3.Normalize(Position - GameState.Avatar.Position);
// calc the rotation so the avatar faces the target
Rotation = Helpers.GetRotation(Vector3.Forward, newForward, Vector3.Up);
Cannon.Shoot(Position, Rotation, this);


public static Quaternion GetRotation(Vector3 source, Vector3 dest, Vector3 up)
{
    float dot = Vector3.Dot(source, dest);

    if (Math.Abs(dot - (-1.0f)) < 0.000001f)
    {
        // vector a and b point exactly in the opposite direction, 
        // so it is a 180 degrees turn around the up-axis
        return new Quaternion(up, MathHelper.ToRadians(180.0f));
    }
    if (Math.Abs(dot - (1.0f)) < 0.000001f)
    {
        // vector a and b point exactly in the same direction
        // so we return the identity quaternion
        return Quaternion.Identity;
    }

    float rotAngle = (float)Math.Acos(dot);
    Vector3 rotAxis = Vector3.Cross(source, dest);
    rotAxis = Vector3.Normalize(rotAxis);
    return Quaternion.CreateFromAxisAngle(rotAxis, rotAngle);
}
Maik Semder
sumber
Ok, saya memberikan suntikan yang dapat Anda lihat melalui edit saya di pertanyaan, kode yang disediakan. Tidak begitu yakin apa masalahnya. input a dan b adalah vektor maju, atau paling tidak anggap sebagai.
Marc
@ Mark lihat versi saya diperbaiki dari kode XNA Anda di jawaban saya. Ada 2 masalah: 1) perhitungan vektor forward baru salah, harus dinormalisasi AvatarPosition - TargetPosition 2) rotAxis harus dinormalisasi setelah lintas-produk di GetRotation
Maik Semder
@ Mark, 2 perubahan kecil: 3) sumber dan dest sudah dinormalisasi, tidak perlu dinormalisasi lagi di GetRotation 4) jangan tes untuk absolut 1 / -1 di GetRotation, daripada menggunakan toleransi, saya menggunakan
0,000001f
Hmm, itu masih belum berfungsi. Hal penskalaan yang sama terjadi dengan model dan target tidak berputar ke arah avatar (yang saya perhatikan di komentar Anda, Anda mencoba memutar avatar ke target .... harus sebaliknya) ... pada dasarnya, mencoba untuk mendapatkan massa untuk menghadapi pemain (avatar dalam kamera orang ketiga). Tidakkah metode GetRotation mengetahui sesuatu tentang rotasi target dan avatar saat ini, seperti, apakah menurut Anda newForward sedang dibuat dengan benar?
Marc
Jika objek diskalakan, itu berarti angka empat tidak memiliki panjang satuan, itu berarti rotAxis tidak dinormalisasi. Apakah Anda menambahkan perubahan kode terakhir saya dengan normalisasi rotAxis? Namun, tolong tunjukkan kode Anda saat ini dan untuk contoh kasus di mana ia tidak berfungsi, silakan juga memposting nilai-nilai: newForward rotAngle rotAxisdan returned quaternion. Kode untuk massa akan sama, setelah kami membuatnya bekerja dengan avatar, akan mudah untuk mengubah judul objek apa pun.
Maik Semder