Извлечь перевод/вращение/масштаб из simd_float4x4

Я хочу извлечь три компонента матрицы преобразования 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

Я не знаком с swift/swift-simd, но будет ли достаточно нормализовать столбцы матрицы перед передачей их в simd_quatf? У вас все еще будут проблемы, если ваше входное преобразование также включает перекос или если оно фактически зеркально отражается, но это, похоже, не имеет отношения к вашему случаю.

chtz 13.02.2023 03:49

Я уже прочитал ваш пост, и мне жаль, что вы продолжаете неправильно понимать вопрос.

wildpeaks 13.02.2023 17:06

Для контекста (поскольку довольно много комментариев, по-видимому, было удалено), мой ответ не был направлен на chtz.

wildpeaks 14.02.2023 00:24
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Инструменты для веб-скрапинга с открытым исходным кодом: Python Developer Toolkit
Веб-скрейпинг, как мы все знаем, это дисциплина, которая развивается с течением времени. Появляются все более сложные средства борьбы с ботами, а...
Калькулятор CGPA 12 для семестра
Калькулятор CGPA 12 для семестра
Чтобы запустить этот код и рассчитать CGPA, необходимо сохранить код как HTML-файл, а затем открыть его в веб-браузере. Для этого выполните следующие...
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
ONLBest Online HTML CSS JAVASCRIPT Training In INDIA 2023
О тренинге HTML JavaScript :HTML (язык гипертекстовой разметки) и CSS (каскадные таблицы стилей) - две основные технологии для создания веб-страниц....
Как собрать/развернуть часть вашего приложения Angular
Как собрать/развернуть часть вашего приложения Angular
Вам когда-нибудь требовалось собрать/развернуть только часть вашего приложения Angular или, возможно, скрыть некоторые маршруты в определенных средах?
Запуск PHP на IIS без использования программы установки веб-платформы
Запуск PHP на IIS без использования программы установки веб-платформы
Установщик веб-платформы, предлагаемый компанией Microsoft, перестанет работать 31 декабря 2022 года. Его закрытие привело к тому, что мы не можем...
Оптимизация React Context шаг за шагом в 4 примерах
Оптимизация React Context шаг за шагом в 4 примерах
При использовании компонентов React в сочетании с Context вы можете оптимизировать рендеринг, обернув ваш компонент React в React.memo сразу после...
1
3
258
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Разложение матрицы

Используйте следующую схему при разборе 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 — это часть, в которой отсутствует правильная формула.

wildpeaks 13.02.2023 08:46

Кроме того, моей первой реакцией перед публикацией вопроса было: «Даже если числа разные, это может быть одно и то же вращение, потому что я знаю, что кватернионы могут выражать одно и то же преобразование несколькими способами». К сожалению, это не так, значения действительно разные: примените два значения поворота к разным AnchorEntity, и вы увидите, что «фактические» и «ожидаемые» значения визуально не совпадают.

wildpeaks 13.02.2023 09:18

Спасибо за усилия, однако вопрос о замене simd_quatf(matrix) другой формулой (потому что это неправильная формула), без использования Transform.

wildpeaks 13.02.2023 09:23
Ответ принят как подходящий

Расширение

Это добавляет свойства 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-решение для ротации, но пока, по крайней мере, оно охватывает почти все случаи.

wildpeaks 13.02.2023 16:59

Другие вопросы по теме