В принципе, у вас есть 2 основных варианта работы, например. с каркасом.
- Использование 4x4 матрица (которая позволяет вращение и перевод)
- использования (блок) кватернионов для вращения и смещения для перевода.
Если вы посмотрите на типичную реализацию функции, взяв 2 вектора и вернув кватернион, давая вращение между ними, вы увидите, что это не просто простая формула. Пограничные случаи идентифицируются и позаботятся.
let rotFromVectors (v1 : vec3) (v2 : vec3) : quat =
let PI = System.Math.PI
let PI_BY_TWO = PI/2.0
let TWO_PI = 2.0 * PI
let ZERO_ROTATION = quat(0.0f,0.0f,0.0f,1.0f)
let aabb = sqrt (float (vec3.dot(v1, v1)) * float (vec3.dot(v2,v2)))
if aabb <> 0.0
then
let ab = float (vec3.dot(v1,v2))/aabb
let c =
vec3
(float32 ((float v1.y * float v2.z - float v1.z * float v2.y)/aabb)
, float32 ((float v1.z * float v2.x - float v1.x * float v2.z)/aabb)
, float32 ((float v1.x * float v2.y - float v1.y * float v2.x)/aabb)
)
let cc = float (vec3.dot(c, c))
if cc <> 0.0
then
let s =
match ab > -sin (PI_BY_TWO) with //0.707107f
| true -> 1.0 + ab
| false -> cc/(1.0 + sqrt (1.0-cc))
let m = sqrt (cc + s * s)
quat(float32 (float c.x/m), float32 (float c.y/m), float32 (float c.z/m), float32(s/m))
else
if ab > 0.0
then
ZERO_ROTATION
else
let m = sqrt (v1.x * v1.x + v1.y * v1.y)
if(m <> 0.0f)
then
quat(v1.y/m, (-v1.x)/m, 0.0f, 0.0f)
else
quat(1.0f,0.0f,0.0f,0.0f)
else
ZERO_ROTATION
Где quat
типа для кватерниона и vec3
типа 3D-вектора в приведенной выше коде.
код для поворота вектора на кватернион примерно столь же просто, как математика говорит:
let rotateVector (alpha : quat) (v:vec3) : vec3 =
let s = vec3.length v
quat.inverse alpha * (vecToPureQuat v) * alpha |> pureQuatToVec |> fun v' -> v' * s
И что не менее важно функции преобразования между (некоторые ... углы Эйлера - есть на самом деле 24 различные версии углов Эйлера, 12 с фиксированными углами вращения и 12 с последовательными вращениями) использует подход с половинным углом.
let eulerToRot (v:vec3) : quat =
let d = 0.5F
let t0 = cos (v.z * d)
let t1 = sin (v.z * d)
let t2 = cos (v.y * d)
let t3 = sin (v.y * d)
let t4 = cos (v.x * d)
let t5 = sin (v.x * d)
quat
( t0 * t3 * t4 - t1 * t2 * t5
, t0 * t2 * t5 + t1 * t3 * t4
, t1 * t2 * t4 - t0 * t3 * t5
, t0 * t2 * t4 + t1 * t3 * t5
)
|> quat.normalize
let rotToEuler (q:quat) : vec3 =
let ysqr = q.y * q.y
// roll (x-axis rotation)
let t0 = +2.0f * (q.w * q.x + q.y * q.z)
let t1 = +1.0f - 2.0f * (q.x * q.x + ysqr)
let roll = atan2 t0 t1
// pitch (y-axis rotation)
let t2 =
let t2' = +2.0f * (q.w * q.y - q.z * q.x)
match t2' with
| _ when t2' > 1.0f -> 1.0f
| _ when t2' < -1.0f -> -1.0f
| _ -> t2'
let pitch = asin t2
// yaw (z-axis rotation)
let t3 = +2.0f * (q.w * q.z + q.x *q.y)
let t4 = +1.0f - 2.0f * (ysqr + q.z * q.z)
let yaw = atan2 t3 t4
vec3(roll,pitch,yaw)
Финальный трюк, чтобы знать, в том, что превратить вектор в (чистом) кватернион пригождается для функции rotateVector
.
let vecToPureQuat (v:vec3) : quat =
quat(v.x,v.y,v.z,0.0f)
let pureQuatToVec (q:quat) : vec3 =
vec3(q.x,q.y,q.z)
Итак, чтобы ответить на ваш главный вопрос: нужны ли кватернионы? Нет. Вы также можете использовать матрицы 4х4.
И вы можете перейти от одного к другому, если он сочтет вас полезным:
let offsetAndRotToMat (offset:vec3) (q:quat) : mat4 =
let ux = v3 1 0 0
let uy = v3 0 1 0
let uz = v3 0 0 1
let rx = rotateVector q ux
let ry = rotateVector q uy
let rz = rotateVector q uz
mat4
(
rx.x, rx.y, rx.z, 0.0f,
ry.x, ry.y, ry.z, 0.0f,
rz.x, rz.y, rz.z, 0.0f,
offset.x,offset.y,offset.z,1.0f
)
Спасибо! Я сделал еще несколько исследований, и я думаю, что постоянная ценность кватернионов просто отбросила меня. Я пытался понять, как различие кватернионов приведет к углам, но теперь я понимаю, что вычисление кватерниона вращения от одного кватерниона к другому кватерниону может быть просто преобразовано в его 3D-углы, чтобы найти разницу в углах. Я читал в углах поворота Эйлера, и я понятия не имел, что порядок, который вы делали, имеет значение. На самом деле я обнаружил, что одно конкретное вращение было минимальным сингулярным случаем. – Andrew