Статьи

Собираюсь в Old School на Windows 8


На другой день я просматривал некоторые архивы на старом дисководе и наткнулся на
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
>

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