Главная » Разработка для Windows Phone 7 » Панорамирование и масштабирование Windows Phone 7

0

Весь остальной код перегруженного Update посвящен обработке сенсорного ввода. Идея здесь проста: любое касание, перемещение, изменение масштаба не приводит к необратимым изменениям. Эффект перемещения и масштабирования обеспечивается изменением объекта Matrix под именем drawMatrix (Матрица рисования), которая используется в вызове Begin объекта SpriteBatch.

Но как только палец или пальцы пользователя снимаются с экрана, приложение меняет массивы PixelInfo и pixels соответственно новому расположению и масштабу экрана. Не делается никакой попытки сохранить что-либо, что вышло за границы области просмотра.

Рассмотрим обработку жестов FreeDrag и DragComplete для операций переноса: Проект XNA: MandelbrotSet Файл: Game1.cs (фрагмент)

protected override void Update(GameTime gameTime) {

// Чтение жестов касания

while (TouchPanel.IsGestureAvailable) {

GestureSample gesture = TouchPanel.ReadGesture();

switch (gesture.GestureType) {

case GestureType.FreeDrag:

// Корректируем drawMatrix соответственно смещению drawMatrix.M41 += gesture.Delta.X; drawMatrix.M4 2 += gesture.Delta.Y; break;

case GestureType.DragComplete:

// Обновляем текстуру пикселов массивом pixelInfos с учетом смещения

lock (pixelInfosLock) {

pixelInfos = TranslatePixelInfo(pixelInfos, drawMatrix); for (int pixelIndex = 0; pixelIndex < pixelInfos.Length;

pixelIndex++)

pixels[pixelIndex] = pixelInfos[pixelIndex].packedColor;

PixelInfo.hasNewColors = false; PixelInfo.firstNewIndex = Int32.MaxValue; PixelInfo.lastNewIndex = 0;

}

texture.SetData<uint>(pixels);

drawMatrix = Matrix.Identity; globalIteration = 0;

break;

UpdateCoordinateText();

base.Update(gameTime);

}

Когда пользователь водит пальцем по экрану, меняется только drawMatrix, но когда пользователь снимает палец с экрана, в ходе обработки жеста DragComplete производится вызов метода TranslatePixelInfo (Перенести PixelInfo) для переноса элементов в массив структур PixelInfo соответственно окончательному положению точки касания. К счастью, пикселы, перемещенные из одной части экрана в другую, можно сохранить; пикселы в новом месте изначально закрашиваются черным. Затем Update обеспечивает перенос цветов пикселов из массива PixelInfo в массив pixels и обновляет из него Texture2D. После этого drawMatrix может быть возвращен к единичной матрице.

Метод TranslatePixelInfo использует окончательные коэффициенты переноса drawMatrix для задания новых значений полей PixelInfo.xPixelCoordAtComplexOrigin и PixelInfo.yPixelCoordAtComplexOrigin и выполняет циклический сдвиг членов PixelInfo:

Проект XNA: MandelbrotSet Файл: Game1.cs (фрагмент)

PixelInfo[] TranslatePixelInfo(PixelInfo[] srcPixelInfos, Matrix drawMatrix) {

int x = (int)(drawMatrix.M41 + 0.5); int y = (int)(drawMatrix.M42 + 0.5); PixelInfo.xPixelCoordAtComplexOrigin += x; PixelInfo.yPixelCoordAtComplexOrigin += y;

PixelInfo[] dstPixelInfos = new PixelInfo[srcPixelInfos.Length];

for (int dstY = 0; dstY < PixelInfo.pixelHeight; dstY++) {

int srcY = dstY – y;

int srcRow = srcY * PixelInfo.pixelWidth; int dstRow = dstY * PixelInfo.pixelWidth;

for (int dstX = 0; dstX < PixelInfo.pixelWidth; dstX++) {

int srcX = dstX – x;

int dstIndex = dstRow + dstX;

if (srcX >= 0 && srcX < PixelInfo.pixelWidth && srcY >= 0 && srcY < PixelInfo.pixelHeight)

{

int srcIndex = srcRow + srcX;

dstPixelInfos[dstIndex] = pixelInfos[srcIndex];

}

else {

dstPixelInfos[dstIndex] = new PixelInfo(dstIndex, null);

}

}

}

return dstPixelInfos;

}

При масштабировании используется противоположный подход. По самой природе множества Мандельброта каждая его точка уникальна и не может быть получена на основании значений соседних точек. По этой причине любая операция масштабирования

должна приводить к повторному созданию массива PixelInfo и повторному выполнению всех вычислений с самого начала.

Но пройдет немного времени, и мы увидим такое изображение:

Но визуальные элементы могут быть сохранены как временные приближения. Поэтому Update обрабатывает жест PinchComplete, применяя трансформацию к массиву pixels и затем используя его для задания цветов в массиве PixelInfo. В первое мгновение после увеличения масштаба на экране будет отображаться примерно следующее:

Код обработки жеста Pinch нам знаком. Единственное отличие в том, что в данном случае он определяет, в каком направлении произошло максимальное перемещение: по вертикали или по горизонтали. Эти данные передаются в метод ComputeScaleMatrix (Вычислить матрицу масштабирования):

Проект XNA: MandelbrotSet Файл: Game1.cs (фрагмент)

protected override void Update(GameTime gameTime) {

// Чтение жестов касания

while (TouchPanel.IsGestureAvailable) {

GestureSample gesture = TouchPanel.ReadGesture();

switch (gesture.GestureType) {

case GestureType.Pinch:

bool xDominates = Math.Abs(gesture.Delta.X) + Math.Abs(gesture.Delta2.X) >

Math.Abs(gesture.Delta.Y) +

Math.Abs(gesture.Delta2.Y);

Vector2 oldPoint1 = gesture.Position – gesture.Delta;

Vector2 newPoint1 = gesture.Position;

Vector2 oldPoint2 = gesture.Position2 – gesture.Delta2; Vector2 newPoint2 = gesture.Position2;

drawMatrix *= ComputeScaleMatrix(oldPoint1, oldPoint2, newPoint2,

xDominates);

drawMatrix *= ComputeScaleMatrix(newPoint2, oldPoint1, newPoint1,

xDominates);

break;

case GestureType.PinchComplete:

// Задаем текстуру из увеличенных пикселов pixels = ZoomPixels(pixels, drawMatrix); texture.SetData<uint>(pixels);

// Задаем новые параметры PixelInfo

PixelInfo.xPixelCoordAtComplexOrigin *= drawMatrix.M11; PixelInfo.xPixelCoordAtComplexOrigin += drawMatrix.M41; PixelInfo.yPixelCoordAtComplexOrigin *= drawMatrix.M22; PixelInfo.yPixelCoordAtComplexOrigin += drawMatrix.M4 2; PixelInfo.unitsPerPixel /= drawMatrix.M11;

// Повторно инициализируем PpixelInfos

lock (pixelInfosLock) {

InitializePixelInfo(pixels);

}

drawMatrix = Matrix.Identity;

globalIteration = 0;

break;

}

UpdateCoordinateText();

}

base.Update(gameTime);

}

Метод ComputeScaleMatrix очень похож на метод с таким же названием из приложения DragAndPinch, за исключением того, что он выполняет пропорциональное масштабирование на основании передаваемого в него аргумента типа Boolean:

Проект XNA: MandelbrotSet Файл: Game1.cs (фрагмент)

Matrix ComputeScaleMatrix(Vector2 refPoint, Vector2 oldPoint, Vector2 newPoint,

bool xDominates)

{

float scale = 1; if (xDominates)

scale = (newPoint.X – refPoint.X) / (oldPoint.X – refPoint.X);

else

scale = (newPoint.Y – refPoint.Y) / (oldPoint.Y – refPoint.Y);

if (float.IsNaN(scale) || float.IsInfinity(scale) || scale < 0) {

return Matrix.Identity;

}

scale = Math.Min(1.1f, Math.Max(0.9f, scale));

Matrix matrix = Matrix.CreateTranslation(-refPoint.X, -refPoint.Y, 0); matrix *= Matrix.CreateScale(scale, scale, 1);

matrix *= Matrix.CreateTranslation(refPoint.X, refPoint.Y, 0); return matrix;

Метод ZoomPixels (Масштабировать пикселы), вызываемый для жеста PinchComplete, получает инверсный Matrix и использует его в вычислениях исходных координат пикселов из результирующих координат. К счастью, для инвертирования матрицы необходимо просто вызвать статический метод Matrix.Invert. В более ранней версии приложения вызывался Matrix.CreateScale (приведен выше), в качестве третьего аргумента в него передавался нуль. Это обеспечивало создание неинвертируемой матрицы, и вызов Invert приводил к созданию матрицы со значениями NaN («not a number») во всех полях. Это нехорошо.

Проект XNA: MandelbrotSet Файл: Game1.cs (фрагмент)

uint[] ZoomPixels(uint[] srcPixels, Matrix matrix) {

Matrix invMatrix = Matrix.Invert(matrix); uint[] dstPixels = new uint[srcPixels.Length];

for (int dstY = 0; dstY < PixelInfo.pixelHeight; dstY++) {

int dstRow = dstY * PixelInfo.pixelWidth;

for (int dstX = 0; dstX < PixelInfo.pixelWidth; dstX++) {

int dstIndex = dstRow + dstX;

Vector2 dst = new Vector2(dstX, dstY);

Vector2 src = Vector2.Transform(dst, invMatrix);

int srcX = (int)(src.X + 0.5f);

int srcY = (int)(src.Y + 0.5f);

if (srcX >= 0 && srcX < PixelInfo.pixelWidth && srcY >= 0 && srcY < PixelInfo.pixelHeight)

{

int srcIndex = srcY * PixelInfo.pixelWidth + srcX; dstPixels[dstIndex] = srcPixels[srcIndex];

}

else {

dstPixels[dstIndex] = Color.Black.PackedValue;

}

}

}

return dstPixels;

}

На этом все самое интересное сделано, но в приложениях по вычислению множества Мандельброта считается необходимым каким-то образом отображать текущее положение на комплексной плоскости. Метод UpdateCoordinateText (Обновить отображаемые координаты) отвечает за вычисление координат верхнего левого и нижнего правого углов, их форматирование в объектах StringBuilder и определение того, где они должны отображаться:

Проект XNA: MandelbrotSet Файл: Game1.cs (фрагмент)

void UpdateCoordinateText() {

double xAdjustedPixelCoord =

PixelInfo.xPixelCoordAtComplexOrigin * drawMatrix.M11 + drawMatrix.M41; double yAdjustedPixelCoord =

PixelInfo.yPixelCoordAtComplexOrigin * drawMatrix.M22 + drawMatrix.M4 2; double adjustedUnitsPerPixel = PixelInfo.unitsPerPixel / drawMatrix.M11;

double xUpperLeft = -adjustedUnitsPerPixel * xAdjustedPixelCoord; double yUpperLeft = adjustedUnitsPerPixel * yAdjustedPixelCoord;

upperLeftCoordText.Remove(0, upperLeftCoordText.Length);

upperLeftCoordText.AppendFormat("X:{0} Y:{1}", xUpperLeft, yUpperLeft);

double xLowerRight = xUpperLeft + PixelInfo.pixelWidth * adjustedUnitsPerPixel; double yLowerRight = -yUpperLeft + PixelInfo.pixelHeight * adjustedUnitsPerPixel;

lowerRightCoordText.Remove(0, lowerRightCoordText.Length);

lowerRightCoordText.AppendFormat("X:{0} Y:{1}", xLowerRight, yLowerRight);

Vector2 textSize = segoe14.MeasureString(lowerRightCoordText); lowerRightCoordPosition = new Vector2(viewport.Width – textSize.X,

viewport.Height – textSize.Y);

}

После всего этого метод Draw довольно прост. Заметьте, что методы Begin и End объекта SpriteBatch вызываются дважды. Для первого вызова необходим объект Matrix, который перемещает и масштабирует Texture2D в ходе его обработки, и второй вызов предназначен для работы с текстовыми элементами:

Проект XNA: MandelbrotSet Файл: Game1.cs (фрагмент)

protected override void Draw(GameTime gameTime) {

GraphicsDevice.Clear(Color.Black);

// Отрисовываем представление множества Мальдеброта

spriteBatch.Begin(SpriteSortMode.Immediate, null, null, null, null, null, drawMatrix);

spriteBatch.Draw(texture, Vector2.Zero, null, Color.White,

0, Vector2.Zero, 1, SpriteEffects.None, 0); spriteBatch.End();

// Отрисовываем текстовое представление координат и состояния spriteBatch.Begin();

spriteBatch.DrawString(segoe14, upperLeftCoordText, Vector2.Zero, Color.White); spriteBatch.DrawString(segoe14, lowerRightCoordText,

lowerRightCoordPosition, Color.White); spriteBatch.DrawString(segoe14, upperRightStatusText,

upperRightStatusPosition, Color.White);

spriteBatch.End(); base.Draw(gameTime);

}

Источник: Чарльз Петзольд, Программируем Windows Phone 7, Microsoft Press, © 2011.

По теме:

  • Комментарии