Silverlight 2で3D(パクリ)

id:nitoyonさんがおもしろいことをやっていたので、Silverlightでマネをしてみた。

ActionScriptで3Dエンジンを一から作ってみようという話。ActionScript自体はあまり知らないけど3Dプログラミング自体には前から興味があったので、楽しく読ませて頂きました。

数学が苦手なので理屈がまだよくわからないけど、Silverlight2で同じのを作ってみました。

以下ソース

Page.xaml

<UserControl x:Class="Silverlight3D.Page"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" >
    
    <Grid Width="400" Height="400" Background="White" Margin="6"
          HorizontalAlignment="Left" VerticalAlignment="Top" MouseMove="Grid_MouseMove" MouseLeave="Grid_MouseLeave">
        <Border BorderBrush="Black" BorderThickness="1" />

        <Canvas x:Name="canvas" Margin="200,200,0,0" />
    </Grid>
    
</UserControl>

Page.xaml.cs

using System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Controls;

namespace Silverlight3D {
    public partial class Page : UserControl {
        private static readonly Brush lineBrush = new SolidColorBrush(Colors.Black);
        private static readonly Brush pointBrush = new SolidColorBrush(Colors.Blue);

        private Point3D p1 = new Point3D { X = 0, Y = 0, Z = 100 };
        private Point3D p2 = new Point3D { X = 100, Y = 0, Z = 0 };
        private Point3D p3 = new Point3D { X = 0, Y = 100, Z = 0 };
        private Point3D p4 = new Point3D { X = -50, Y = -50, Z = -50 };

        private Point? prevPos;

        public Page() {
            InitializeComponent();

            Draw3D(0, 0);
        }

        private void Draw3D(double rotateX, double rotateY) {
            this.canvas.Children.Clear();

            p1 = Rotate(p1, rotateX, rotateY);
            p2 = Rotate(p2, rotateX, rotateY);
            p3 = Rotate(p3, rotateX, rotateY);
            p4 = Rotate(p4, rotateX, rotateY);

            DrawLine(p1, p2); DrawLine(p1, p3); DrawLine(p1, p4);
            DrawLine(p2, p3); DrawLine(p2, p4);
            DrawLine(p3, p4);

            DrawPoints(p1, p2, p3, p4);
        }

        private void DrawLine(Point3D p1, Point3D p2) {
            var line = new Line {
                X1 = p1.X,
                X2 = p2.X,
                Y1 = p1.Y,
                Y2 = p2.Y,
                Stroke = lineBrush,
                StrokeThickness = 1.0
            };
            this.canvas.Children.Add(line);
        }

        private void DrawPoints(params Point3D[] points) {
            foreach(var p in points) {
                var circle = new Ellipse {
                    Width = 10,
                    Height = 10,
                    Fill = pointBrush
                };
                Canvas.SetLeft(circle, p.X - circle.Width / 2);
                Canvas.SetTop(circle, p.Y - circle.Height / 2);

                this.canvas.Children.Add(circle);
            }
        }

        private Point3D Rotate(Point3D origin, double x, double y) {
            var result = origin.Clone();
            // y方向
            var p = Rotate2D(result.Z, result.X, x / 180 * Math.PI);
            result.Z = p.X;
            result.X = p.Y;
            // z方向
            p = Rotate2D(result.X, result.Y, -y / 180 * Math.PI);
            result.X = p.X;
            result.Y = p.Y;

            return result;
        }

        private Point Rotate2D(double x, double y, double rad) {
            return new Point(
                Math.Cos(rad) * x + Math.Sin(rad) * y,
                -Math.Sin(rad) * x + Math.Cos(rad) * y
            );
        }

        private void Grid_MouseMove(object sender, MouseEventArgs e) {
            var curPos = e.GetPosition(canvas);

            if(this.prevPos != null) {
                Draw3D(curPos.X - prevPos.Value.X, curPos.Y - prevPos.Value.Y);
            }
            this.prevPos = curPos;
        }
        private void Grid_MouseLeave(object sender, MouseEventArgs e) { this.prevPos = null; }
    }

    public class Point3D {
        public double X {
            get;
            set;
        }
        public double Y {
            get;
            set;
        }
        public double Z {
            get;
            set;
        }

        public Point3D Clone() { return (Point3D)this.MemberwiseClone(); }
    }
}

元のActionScript版だと86行ということだったけど、Silverlight版だと115行(CSharp)+ 12行(XAML)で127行になってしまった。負けた・・・orz(いや、空行とかを切り詰めれば。。。)

既にこの段階でついていけてないけど、これはじっくり腰を据えて勉強してみたい分野ですね。

ソース