Движение и анимация в трехмерном пространстве

81

При использовании класса DrawingSurface только для рисования статических сцен вы упускаете наиболее важные преимущества трехмерной модели. Приложение можно сделать намного интереснее, заставив графические объекты двигаться.

Существует много причин, побуждающих объединять средства трехмерного вывода с анимацией. Иногда нужно предоставить пользователю возможность повернуть объект, чтобы увидеть его под другим углом или с другой стороны. В некоторые приложения встроены целые виртуальные миры, позволяющие проходить по трехмерной сцене виртуальной реальности. В любом случае начинать нужно с подготовки базовых строительных блоков кода, которые мы рассмотрим в следующих разделах.

Перемещение трехмерной фигуры

Перемещение или поворот любой фигуры (единственного треугольника, относительно простого текстурированного куба или сложной модели, созданной с помощью специальных инструментов) осуществляется путем изменения мировой матрицы.

Во всех предыдущих примерах в качестве мировой матрицы (она хранится в свойстве BasicEffect.World) использовалась единичная матрица (identity matrix):

effect.World = Matrix.Identity;

Это означает, что в предыдущих примерах мировая матрица фактически ничего не делала. Однако рассмотрим более интересный код:

Matrix translation = Matrix.CreateTranslation(-2, 0, 0);
effect.World = effect.World * translation;

Здесь мировая матрица изменяется путем трансляции. Трансляция — это операция сдвига координат объекта на заданную величину. В данном примере процедура трансляции перемещает объект на две единицы влево (от координаты X отнимаются две единицы, а координаты Y и Z не изменяются).

Не забывайте, что в трехмерных сценах не используются физические единицы (такие, как пиксели). Перемещение на две единицы влево может означать в одних случаях перемещение на большое расстояние, а в других - на малое. Это зависит от того, как близко к объекту расположена камера и насколько велико поле обзора.

Применить трансляцию очень легко. Метод Matrix.CreateTranslation() автоматически генерирует необходимую матрицу преобразования, на которую нужно умножить мировую матрицу вида. Вам не нужно беспокоиться о матричной алгебре и формулах, используемых для умножения и преобразования. Тем не менее следует помнить, что операция

effect.World = effect.World * translation;

не эквивалентна операции

effect.World = translation * effect.World;

Умножение матриц не коммутативная операция, поэтому в инструкции умножения важна последовательность расположения операндов. Мировая матрица всегда должна быть слева, а матрица трансляции — справа. Для удобства (и чтобы легче было запомнить) можно использовать оператор *=.

А вот одна из распространенных ошибок:

effect.World = translation;

Эта инструкция фактически замещает мировую матрицу матрицей трансляции, хотя нужно не заместить, а преобразовать ее.

И наконец, важно помнить, что преобразование, выполняемое над мировой матрицей, является кумулятивной операцией. Это означает, что последовательность преобразований можно выполнить за один раз.

Поворот фигуры

Метод CreateTranslation() класса Matrix позволяет перемещать объект. Для поворота объекта в классе Matrix есть другие методы. Поскольку поворот — более сложная операция, чем перемещение, для его выполнения предназначено более одного метода:

Методы класса Matrix, предназначенные для поворота фигур
Описание Метод
CreateRotationX() Поворот фигуры относительно оси X. Угол поворота задается в радианах
CreateRotationY() Поворот фигуры относительно оси Y. Угол поворота задается в радианах
CreateRotationZ() Поворот фигуры относительно оси Z. Угол поворота задается в радианах
CreateFromYawPitchRoll() Поворот фигуры одновременно относительно всех трех осей. В аргументах применяется авиационная терминология (показанная на рисунке ниже): pitch — угол тангажа (вокруг оси X), yaw — угол рыскания (вокруг оси Y); roll — угол крена (вокруг оси Z)
CreateFromAxisAngle() Поворот фигуры относительно оси, заданной вектором, который не обязательно должен совпадать с осью X, Y или Z
Поворот объекта вокруг трех осей

Следующий код поворачивает объект на одну восьмую полного круга (как вы помните, один круг эквивалентен углу 2π радиан, а константа PiOver4 равна π/4):

Matrix rotation = Matrix.CreateRotationY(MathHelper.PiOver4);
effect.World *= rotation;

Как и при перемещении, поворот выполняется путем умножения мировой матрицы на матрицу преобразования. Необходимо учитывать расстояние между объектом и осью поворота. Например, если ось Z проходит через середину объекта, то матрица, созданная методом CreateRotationZ(), повернет объект таким образом, что его центр останется на прежнем месте.

Если же объект находится на большом расстоянии от оси, матрица преобразования переместит его, как планету вокруг Солнца. Если нужно, чтобы фигура правильно повернулась вокруг оси Z, необходимо провести ось Z через середину фигуры. Поэтому центральную точку фигуры часто помещают в точку с координатами (0,0,0).

Когда объект будет готов к преобразованию, сначала выполните поворот, а затем переместите объект в нужное место. Эти две операции можно выполнить в одной инструкции:

effect.World *= translation * rotation;

Класс Matrix предоставляет еще один метод преобразования — CreateScale(). Он позволяет изменить масштаб объекта — увеличить или уменьшить его. Существует несколько перегруженных версий метода CreateScale(), предназначенных для масштабирования с нарушением пропорций вдоль осей X, Y и Z. Например, можно растянуть объект в два раза вдоль оси X, и в три раза вдоль оси Y и оставить неизменным вдоль оси Z.

Анимация фигур

Теперь вы знаете все, что необходимо для анимации трехмерной фигуры. В первую очередь нужно убедиться в том, что логика рисования выполняется непрерывно. Для этого в конец обработчика события DrawingSurface.Draw должна быть добавлена следующая инструкция:

e.InvalidateSurface();

Далее следует всего лишь применить операцию поворота при каждом возникновении события Draw.

Кроме того, необходимо вычислить, на какой угол должна быть повернута фигура при каждом возникновении события Draw. Чтобы изображение не мелькало, это должен быть очень маленький угол, обычно намного меньше величины MathHelper.PiOver4.

Однако, разные видеокарты выполняют код рисования с разной скоростью. Чтобы скорость вращения была одинаковой на каждом компьютере, нужно связать угол поворота с интервалом времени, прошедшим между двумя событиями Draw. Тогда быстрая видеокарта, способная нарисовать больше кадров в секунду повернет фигуру на меньший угол, а медленная видеокарта — на больший угол, и в обоих случаях скорость поворота будет одна и та же.

Объект DrawEventArgs предоставляет два свойства, помогающих использовать информацию о времени рисования в процедуре анимации. Свойство TotalTime возвращает объект типа TimeSpan, содержащий общее время, прошедшее с момента запуска приложения. Более полезное свойство DeltaTime возвращает объект TimeSpan, содержащий время, прошедшее с момента последнего события Draw (технически это разность между двумя значениями TotalTime в момент генерации последнего события Draw и в текущий момент времени).

Ниже приведен обработчик события Draw, в котором свойство DeltaTime используется для настройки скорости вращения текстурированного куба:

private void DrawingSurface_Draw_1(object sender, DrawEventArgs e)
{
            GraphicsDevice device = GraphicsDeviceManager.Current.GraphicsDevice;
            device.Clear(new Color(0, 0, 0));

            // Поворот куба
            Matrix rotation = Matrix.CreateRotationY(
                MathHelper.PiOver4 * (float)e.DeltaTime.TotalMilliseconds / 2000f);
            cube.World *= rotation;

            cube.Draw(device);

            // Обновление поверхности рисования
            e.InvalidateSurface();
}
Вращение куба
Пройди тесты
Лучший чат для C# программистов