Я хочу извлечь три компонента матрицы преобразования simd_float4x4 без вспомогательных средств, специфичных для движка (таких как RealityKit Transform).
Я уже нашел способ извлечения translation и scale, но мне не хватает rotation.
Самое близкое, что я нашел, это simd_quatf(matrix), но оно не работает, когда матрица преобразования имеет scale.
let transform = Transform(
scale: simd_float3(1, 2, 3),
rotation: simd_quatf(angle: 0.5, axis: .init(0, 1, 0)),
translation: simd_float3(10, 20, 30)
)
let matrix = transform.matrix
// Translation: OK
let translation = simd_float3(
matrix.columns.3.x,
matrix.columns.3.y,
matrix.columns.3.z
)
print("Expected: \( transform.translation )")
print("Actual: \( translation )")
// Expected: SIMD3<Float>(10.0, 20.0, 30.0)
// Actual: SIMD3<Float>(10.0, 20.0, 30.0)
// Scale: OK
let scale = simd_float3(
simd_length(simd_float3(matrix.columns.0.x, matrix.columns.0.y, matrix.columns.0.z)),
simd_length(simd_float3(matrix.columns.1.x, matrix.columns.1.y, matrix.columns.1.z)),
simd_length(simd_float3(matrix.columns.2.x, matrix.columns.2.y, matrix.columns.2.z))
)
print("Expected: \( transform.scale )")
print("Actual: \( scale )")
// Expected: SIMD3<Float>(1.0, 2.0, 3.0)
// Actual: SIMD3<Float>(1.0, 2.0, 3.0)
// Rotation: doesn't match
let rotation = simd_quatf(matrix)
print("Expected: \( transform.rotation )")
print("Actual: \( rotation )")
// Expected: simd_quatf(real: 0.9689124, imag: SIMD3<Float>(0.0, 0.24740396, 0.0))
// Actual: simd_quatf(real: 1.2757674, imag: SIMD3<Float>(0.0, 0.37579384, 0.0))
Я знаю, что можно извлечь значение поворота, потому что Transform может это сделать, я просто не знаю, как оно рассчитывается внутри.
В этом примере оба Transform извлекают одно и то же значение поворота, несмотря на то, что второе преобразование имеет только матрицу:
// Initialize from components
let transform1 = Transform(
scale: simd_float3(10, 20, 30),
rotation: simd_quatf(angle: 0.5, axis: .init(0, 1, 0)),
translation: simd_float3(1, 2, 3)
)
// Initialize from composed matrix
let matrix = simd_float4x4([
[8.7758255, 0.0, -4.7942553, 0.0],
[0.0, 20.0, 0.0, 0.0],
[14.382767, 0.0, 26.327477, 0.0],
[1.0, 2.0, 3.0, 1.0]
])
let transform2 = Transform(matrix: matrix)
// Both extract the same value:
// simd_quatf(real: 0.9689124, imag: SIMD3<Float>(0.0, 0.24740396, 0.0))
print( transform1.rotation )
print( transform2.rotation )
// This doesn't extract the same value:
// simd_quatf(real: 3.745107, imag: SIMD3<Float>(0.0, 1.2801384, 0.0))
print( simd_quatf(matrix) )
// They don't match visually either
anchor1.orientation = transform1.rotation
anchor2.orientation = transform2.rotation // same as anchor 1
anchor3.orientation = simd_quatf(matrix) // different from anchor 1 & 2
Я уже прочитал ваш пост, и мне жаль, что вы продолжаете неправильно понимать вопрос.
Для контекста (поскольку довольно много комментариев, по-видимому, было удалено), мой ответ не был направлен на chtz.
Используйте следующую схему при разборе RealityKit matrix4x4:
┌ ┐
| a b c d |
| e f g h |
| i j k l |
| 0 0 0 1 |
└ ┘
Этот метод позволяет разложить матрицу ModelEntity или AnchorEntity и получить отдельно векторы масштаба, ориентации и положения.
import UIKit
import RealityKit
extension ViewController {
func decomposingMatrixOf(_ entity: Entity) -> (scale: SIMD3<Float>,
orientation: simd_quatf,
position: SIMD3<Float>) {
// SCALE EXTRACTION
// only positive scale is considered in this example
let a = entity.transform.matrix.columns.0.x
let e = entity.transform.matrix.columns.0.y
let i = entity.transform.matrix.columns.0.z
let b = entity.transform.matrix.columns.1.x
let f = entity.transform.matrix.columns.1.y
let j = entity.transform.matrix.columns.1.z
let c = entity.transform.matrix.columns.2.x
let g = entity.transform.matrix.columns.2.y
let k = entity.transform.matrix.columns.2.z
let xScale = sqrt((a * a) + (e * e) + (i * i))
let yScale = sqrt((b * b) + (f * f) + (j * j))
let zScale = sqrt((c * c) + (g * g) + (k * k))
let scale = SIMD3<Float>(xScale, yScale, zScale)
// ORIENTATION EXTRACTION
let orientation = entity.transform.rotation
// POSITION EXTRACTION
let d = entity.transform.matrix.columns.3.x
let h = entity.transform.matrix.columns.3.y
let l = entity.transform.matrix.columns.3.z
let position = SIMD3<Float>(d, h, l)
return (scale, orientation, position)
}
}
class ViewController: UIViewController {
@IBOutlet var arView: ARView!
override func viewDidLoad() {
super.viewDidLoad()
var matrixS = simd_float4x4()
matrixS.columns.0.x = 2.0
matrixS.columns.1.y = 2.5
matrixS.columns.2.z = 1.5
let model = ModelEntity(mesh: .generateBox(size: 0.25))
let rotationTransform = Transform(pitch: 0, yaw: .pi/6, roll: 0)
print(rotationTransform.rotation) // 0
// your formula is just a part of transform
let matrixR = Transform(scale: .one,
rotation: simd_quatf(rotationTransform.matrix),
translation: .zero).matrix
let matrixSR = simd_mul(matrixR, matrixS)
model.transform.matrix = matrixSR
model.position.z = -0.5
let anchor = AnchorEntity()
anchor.addChild(model)
arView.scene.anchors.append(anchor)
// Decomposing
print( self.decomposingMatrixOf(model).scale ) // 1
print( model.scale ) // 2
print( self.decomposingMatrixOf(model).orientation ) // 3
print( model.transform.rotation ) // 4
print( self.decomposingMatrixOf(model).position ) // 5
print( model.position ) // 6
}
}
Результаты в консоли:
(real и imaginary части построенного кватерниона здесь согласованы)
simd_quatf(real: 0.9659258, imag: SIMD3<Float>(0.0, 0.258819, 0.0)) // 0
SIMD3<Float>(2.0, 2.5, 1.5) // 1
SIMD3<Float>(2.0, 2.5, 1.5) // 2
simd_quatf(real: 0.9659258, imag: SIMD3<Float>(0.0, 0.258819, 0.0)) // 3
simd_quatf(real: 0.9659258, imag: SIMD3<Float>(0.0, 0.258819, 0.0)) // 4
SIMD3<Float>(0.0, 0.0, -0.5) // 5
SIMD3<Float>(0.0, 0.0, -0.5) // 6
Simd_quatf.init(_ rotationMatrix: simd_float4x4) учитывает только ориентацию объекта и не учитывает его реальный масштаб и положение. Таким образом, чтобы матрица преобразования была полной, необходимы значения всех 16 ячеек.
Матрица RealityKit 4x4, как и любая другая simd-матрица 4x4 , не содержит ЧИСТЫХ значений поворота. Вращение — это продукт сдвига и масштаба. Поэтому, извлекая из матрицы, например, значения Y-поворота, вы получите данные, расположенные в 4 ячейках матрицы.
Причем результат вращения вычисляется с помощью четырех тригонометрических функций. Извлечение значений вращения с помощью .init(real:imag:) дает вам следующий результат. Все расчеты поворотов в RealityKit, ARKit и SceneKit выполняются в радианах.
let transform = Transform(scale: SIMD3<Float>(1, 2, 3),
rotation: simd_quatf(angle: .pi/6, axis: [0, 1, 0]),
translation: SIMD3<Float>(10, 20, 30))
Результирующая матрица:
┌ ┐
| 0.86 0.00 1.49 10.00 |
| 0.00 2.00 0.00 20.00 |
| -0.49 0.00 2.59 30.00 |
| 0.00 0.00 0.00 1.00 |
└ ┘
Если вы напечатаете transform.rotation, вы получите следующий результат:
simd_quatf(real: 0.9659258, imag: SIMD3<Float>(0.0, 0.258819, 0.0))
Когда вы печатаете эти подсвойства, вы получите начальные значения:
print(transform.rotation.angle)
print(transform.rotation.axis)
0.52359873 // Float.pi/6
SIMD3<Float>(0.0, 1.0, 0.0) // Y axis
Вот что говорится в официальной документации Apple о var matrix: float4x4 {get set}
Компонент Transform не может представлять все преобразования, которые может представлять обычная матрица 4x4. Таким образом, использование матрицы 4x4 для задания преобразования является событием с потерями, которое может привести к отбрасыванию определенных преобразований, таких как сдвиг.
Вот почему ваши результаты значений rotation-only противоречивы. Кроме того, simd_quatf.init(_ rotationMatrix: simd_float4x4) игнорирует последнюю строку и последний столбец матрицы 4x4 (т. е. мы передаем матрицу 3x3 ), поэтому мы теряем переключатель Однородные координаты и значения перевода.
Формула, которую вы пытаетесь вывести, всегда будет содержать противоречивый результат. В RealityKit 2.0 нет возможности, используя только инициализатор simd_quatf, сгенерировать матрицу преобразования 4x4.
Я ценю усилия, но вопрос был о вычислении того же значения, что и Transform.rotation, используя только функции simd (в моем примере Transform только для сравнения, что результат совпадает). Я даже включил способ вычисления translation и scale, чтобы проиллюстрировать тип кода, который я искал, Rotation: doesn't match — это часть, в которой отсутствует правильная формула.
Кроме того, моей первой реакцией перед публикацией вопроса было: «Даже если числа разные, это может быть одно и то же вращение, потому что я знаю, что кватернионы могут выражать одно и то же преобразование несколькими способами». К сожалению, это не так, значения действительно разные: примените два значения поворота к разным AnchorEntity, и вы увидите, что «фактические» и «ожидаемые» значения визуально не совпадают.
Спасибо за усилия, однако вопрос о замене simd_quatf(matrix) другой формулой (потому что это неправильная формула), без использования Transform.
Это добавляет свойства Transform к simd_float4x4.
extension simd_float4x4 {
var translation: simd_float3 {
return [columns.3.x, columns.3.y, columns.3.z]
}
var rotation: simd_quatf {
// This would work only when scale is 1:
// return simd_quatf(self)
return Transform(matrix: self).rotation
}
var scale: simd_float3 {
return [
simd_length(simd_float3(columns.0.x, columns.0.y, columns.0.z)),
simd_length(simd_float3(columns.1.x, columns.1.y, columns.1.z)),
simd_length(simd_float3(columns.2.x, columns.2.y, columns.2.z)),
]
}
}
Затем вы можете использовать его непосредственно из значения матрицы:
let matrix = simd_float4x4( ... )
print( matrix.translation )
print( matrix.rotation )
print( matrix.scale )
Потому что вопрос, как я уже несколько раз заявлял, заключался в вычислении значений перемещения/поворота/масштаба из значения simd_float4x4 без использования Transform. Этот ответ делает именно то, что я просил, дополнительно отформатированный как расширение, чтобы упростить его использование. Хотелось бы иметь и чистое SIMD-решение для ротации, но пока, по крайней мере, оно охватывает почти все случаи.
Я не знаком с swift/swift-simd, но будет ли достаточно нормализовать столбцы матрицы перед передачей их в simd_quatf? У вас все еще будут проблемы, если ваше входное преобразование также включает перекос или если оно фактически зеркально отражается, но это, похоже, не имеет отношения к вашему случаю.