На другой день я просматривал некоторые архивы на старом дисководе и наткнулся на
T64 отформатированных исполняемые файлы некоторых
демо программного обеспечения , которое я написал для Commodore 64 в 1980 году . Демонстрационное программное обеспечение увлекательно писать, оно больше посвящено уловкам и оптимизации, чем бизнес-правилам или моделям данных. Я написал несколько забавных проектов в Silverlight несколько лет назад и решил преобразовать некоторые из них в Windows 8. Этот конкретный проект включал использование
плоской проекции на шесть различных прямоугольников, чтобы создать иллюзию куба (это иллюзия, потому что это не так. настоящий 3D-рендеринг, но вместо этого манипуляции с прямоугольниками создают иллюзию 3D-конструкции). Это было наложено на классическое плазменное поле на основе синуса
.
Еще раз, я был поражен тем, как быстро и легко мне удалось перенести код на Windows 8. По сути, единственной реальной работой было изменение реализации эффекта плазмы, поскольку более старая версия могла манипулировать пикселями непосредственно в растровом изображении. объект, тогда как в Windows 8 это должно быть сделано в буфере и перенесено. Этот код ни в коем случае не оптимизирован и не эффективен — вы наверняка увидите скачок ЦП — но это приятный и забавный эффект, когда смотреть и кодировать его было хорошим отрывом от типичной бизнес-работы.
Первым шагом было создание куба. Стиль определяет свойства «стороны»:
<Style TargetType="Rectangle">
<Setter Property="Margin" Value="170,50"/>
<Setter Property="Height" Value="150"/>
<Setter Property="Width" Value="150"/>
<Setter Property="Opacity" Value="0.5"/>
</Style>
Затем стороны располагаются так, чтобы имитировать фактическую поверхность куба — вот только две из шести сторон в качестве примера. Обратите внимание, что две стороны обернуты в сетку, чтобы облегчить необходимые преобразования для правильного проецирования:
<Rectangle Fill="Orange">
<Rectangle.Projection>
<PlaneProjection x:Name="Rectangle4Projection"
CenterOfRotationZ="-75" RotationX="90"/>
</Rectangle.Projection>
</Rectangle>
<Grid Margin="170,50">
<Grid.Projection>
<PlaneProjection x:Name="Rectangle5Projection"
CenterOfRotationZ="-75" RotationY="-90"/>
</Grid.Projection>
<Rectangle Margin="0" Fill="Yellow" RenderTransformOrigin="0.5,0.5">
<Rectangle.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform x:Name="Rectangle5Rotation" Angle="0"/>
<TranslateTransform/>
</TransformGroup>
</Rectangle.RenderTransform>
</Rectangle>
</Grid>
Когда стороны на месте, сам элемент управления превращается в два события. Первое событие, CompositionTarget.Rendering , возникает каждый раз, когда для приложения отображается кадр. Это один из способов обработки анимации путем «мошенничества» в цикле рендеринга, а не в зависимости от таймера диспетчера. Это также означает, что куб будет двигаться с разной скоростью в зависимости от скорости вашей машины. Это событие используется для обновления прогнозов для сторон:
private void CompositionTargetRendering(object sender, object e)
{
this.Rectangle1Projection.RotationY += ((this.pt.X - (this.LayoutRoot.ActualWidth / 2)) / this.LayoutRoot.ActualWidth) * 10;
this.Rectangle2Projection.RotationY += ((this.pt.X - (this.LayoutRoot.ActualWidth / 2)) / this.LayoutRoot.ActualWidth) * 10;
this.Rectangle3Projection.RotationY += ((this.pt.X - (this.LayoutRoot.ActualWidth / 2)) / this.LayoutRoot.ActualWidth) * 10;
this.Rectangle4Projection.RotationY += ((this.pt.X - (this.LayoutRoot.ActualWidth / 2)) / this.LayoutRoot.ActualWidth) * 10;
this.Rectangle5Projection.RotationY += ((this.pt.X - (this.LayoutRoot.ActualWidth / 2)) / this.LayoutRoot.ActualWidth) * 10;
this.Rectangle6Projection.RotationY += ((this.pt.X - (this.LayoutRoot.ActualWidth / 2)) / this.LayoutRoot.ActualWidth) * 10;
this.Rectangle1Projection.RotationX += ((this.pt.Y - (this.LayoutRoot.ActualHeight / 2)) / this.LayoutRoot.ActualHeight) * 10;
this.Rectangle2Projection.RotationX += ((this.pt.Y - (this.LayoutRoot.ActualHeight / 2)) / this.LayoutRoot.ActualHeight) * 10;
this.Rectangle3Projection.RotationX += ((this.pt.Y - (this.LayoutRoot.ActualHeight / 2)) / this.LayoutRoot.ActualHeight) * 10;
this.Rectangle4Projection.RotationX += ((this.pt.Y - (this.LayoutRoot.ActualHeight / 2)) / this.LayoutRoot.ActualHeight) * 10;
this.Rectangle5Rotation.Angle -= ((this.pt.Y - (this.LayoutRoot.ActualHeight / 2)) / this.LayoutRoot.ActualHeight) * 10;
this.Rectangle6Rotation.Angle += ((this.pt.Y - (this.LayoutRoot.ActualHeight / 2)) / this.LayoutRoot.ActualHeight) * 10;
}
Вы заметите, что есть ссылка на экземпляр pt, который влияет на смещения, примененные к кубу. Этот экземпляр обновляется событием PointerMoved и позволяет вам изменять скорость и направление вращения куба, проводя пальцем, мышью или стилусом по кубу.
private void LayoutRootPointerMoved(object sender,
Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
{
this.pt = e.GetCurrentPoint(this.LayoutRoot).Position;
}
Эффект плазмы также является контролем. Изображение используется для размещения фоновой плазмы:
<Grid>
<Image x:Name="PlasmaImage" Stretch="Fill"/>
</Grid>
Каждый кадр, новая волна синусоидальных данных вычисляется, отображается в пиксельном буфере и применяется к изображению . Виртуальный размер 320 х 200 используется, а затем просто расширяется. Это уменьшает объем вычислений, но хорошо масштабируется до больших экранов, потому что постоянное движение и цветовые сдвиги не дают увидеть отдельные пиксели. Синус-таблица генерируется для эффекта:
private void CreateSineTable()
{
for (var i = 0; i < 512; i++)
{
var rad = (i * 0.703125) * 0.0174532;
this.sine[i] = (int)(Math.Sin(rad) * 1024);
}
}
А также цветовая палитра:
private void CreatePalette()
{
for (var i = 0; i < 64; ++i)
{
var r = i << 2;
var g = 255 - ((i << 2) + 1);
this.palette[i] = Color.FromArgb(255, (byte)r, (byte)g, 0);
g = (i << 2) + 1;
this.palette[i + 64] = Color.FromArgb(255, 255, (byte)g, 0);
r = 255 - ((i << 2) + 1);
g = 255 - ((i << 2) + 1);
this.palette[i + 128] = Color.FromArgb(255, (byte)r, (byte)g, 0);
g = (i << 2) + 1;
this.palette[i + 192] = Color.FromArgb(255, 0, (byte)g, 0);
}
}
Затем применяется некоторая математика для генерации эффекта (я позволю вам проверить код на предмет этого, это довольно распространено, хотя в старые времена, когда его оптимизировали для работы на медленных процессорах, случалось много магии). Когда буфер установлен, он отображается в WriteableBitmap и устанавливается в качестве источника изображения :
var image = new WriteableBitmap(ScreenWidth, ScreenHeight);
using (var stream = image.PixelBuffer.AsStream())
{
stream.Seek(0, SeekOrigin.Begin);
stream.Write(this.pixelBuffer, 0, this.pixelBuffer.Length);
}
image.Invalidate();
this.plasmaImage = image;
Теперь есть «контроль» плазменного эффекта и контроль куба, который можно поместить на холст. Если бы у меня было больше часов в день, я бы оптимизировал это, обновив поле плазмы в фоновом потоке, чтобы поток пользовательского интерфейса просто выполнял операцию буфера. Может также быть возможность использовать параллельные функции TPL — просто не было времени копаться в этом. Эффект хорошо работает как закодированный, даже если он занимает несколько циклов. Главная страница просто накладывает куб на плазменный фон, а затем использует окно просмотра, чтобы растянуть эффекты, чтобы заполнить весь экран:
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<local:Plasma HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
<Viewbox HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Margin="50">
<local:Cube/>
</Viewbox>
</Grid>
Это был забавный побочный проект. Вы можете скачать полный исходный код здесь или загрузить пакет здесь .