На самом деле, результат должен быть другим (даже если вы не рассматриваете накопительную ошибку, как указано выше).Причина в том, что порядок имеет значение при вращении: вращение вокруг X, затем около Y отличается от вращения вокруг Y, затем около X.
В матричной записи (насколько я понимаю ваши настройки) у вас естьследующее идеальное поведение:
let angle = (end - start)/500
Rx = rotate.RotateX(angle)
Ry = rotate.RotateY(angle)
then, foreach frame in (0..500):
cumulative: Rc = Rx * Ry * Rx * Ry * ... * Rx * Ry
= (Rx * Ry)^frame
assignment: Ra = Rx * Rx * ... * Ry * Ry * ....
= (Rx)^frame * (Ry)^frame
Некоторые замечания по псевдокоду: здесь принято, что мы умножаем матрицы слева направо (это означает, что точки являются векторами строк).Кроме того, в случае, если неясно, (matrix)^N
- матричное возведение в степень: умножьте N
копий (matrix)
вместе в последовательности.
Для кумулятивного случая ваш OQ начинается с единичной матрицы, умножает ее нанебольшие вращения Rx
и Ry
подряд;ваш rotation
равен моему (Rx*Ry)
.Затем он умножает worldMatrix
на эту матрицу несколько раз;это означает, что для любого данного кадра worldMatrix
= initial_worldMatrix * (Rx*Ry)^frame
.
Для случая назначения вычисляется угол, равный frame * total_angle/total_frames
.Это эквивалентно вращению total_angle/total_frames
последовательно frame
раз, что важно, потому что эти маленькие вращения точно такие же, как маленькие вращения, используемые в совокупном случае.Так, в случае назначения ваш код вычисляет (Rx)^frame * (Ry)^frame
и каждый раз сбрасывает worldMatrix на это значение.
Дело в том, что это разные матрицы ;даже с идеальной математикой они должны выглядеть по-разному.
Какой из них выбрать, зависит от того, какое поведение вы хотите.Накопительная версия будет близко приближаться к повороту вокруг оси по диагонали между осями X и Y;вместо этого версия назначения действует как вращающиеся гимбалы.
Если вы хотите использовать кумулятивное поведение, есть лучшие способы, чем умножать до 500 матриц вместе (как упоминалось выше, ваши матрицы будут дрейфовать из-за ошибки с плавающей запятой).В частности, вы можете повернуть на 45 градусов вокруг оси Z, затем повернуть кадр / 500 вокруг оси X, а затем повернуть на -45 градусов вокруг оси Z, чтобы получить аналогичный эффект.
Для уточненияразница между этими двумя случаями:
В кумулятивном случае вы вращаетесь немного вокруг X, затем немного относительно Y, повторяете много раз.Если повороты малы, результатом объединения двух маленьких поворотов будет небольшое вращение вокруг некоторой оси (если они не настолько малы, ось может быть не точно между ними, новсе равно будет конкретный поворот).Дело в том, что если вы повторите эту пару вращений, результатом будет все большее и большее вращение на этой оси, какой бы она ни была.
В случае назначения вы выполняете все свои вращения вокруг X, затемвсе ваши вращения вокруг Y. Это делает вращения большими, и это имеет значение.Один из способов визуализировать разницу состоит в том, чтобы представить набор координатных осей: большое вращение X будет вращать исходную ось Y вне линии, так что вращение Y применяется по-разному.
В математических терминах причинапочему такая большая разница заключается в том, что в общем случае вращения не являются коммутативными: другими словами, порядок имеет значение.Обратите внимание, что небольшие повороты приблизительно коммутативны (когда угол поворота приближается к нулю, разница между Rx * Ry
и Ry * Rx
приближается к нулю квадратично - сокращение угла пополам уменьшает разницу в 4 раза), но когда вы объединяете все мини-вращения в два больших, вы делаете так много переупорядочивания, что это имеет огромное значение.Даже если каждый отдельный своп (Rx * Ry
-> Ry * Rx
) имеет лишь незначительный эффект, перенос N Rx
в одну сторону фактически является пузырьковой сортировкой: вам потребуется O (N ^ 2) свопов длясделай это ....