첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
728x90
반응형
728x170

TestProject.zip
다운로드

▶ PanoramaViewer.cs

using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Media3D;

namespace TestProject
{
    /// <summary>
    /// 파노라마 뷰어
    /// </summary>
    public class PanoramaViewer : Viewport3D
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 시야각 속성 - FieldOfViewProperty

        /// <summary>
        /// 시야각 속성
        /// </summary>
        public static readonly DependencyProperty FieldOfViewProperty = DependencyProperty.Register
        (
            "FieldOfView",
            typeof(double),
            typeof(PanoramaViewer),
            new PropertyMetadata
            (
                (double)0,
                new PropertyChangedCallback(FieldOfViewPropertyChangedCallback)
            )
        );

        #endregion
        #region X축 회전 속성 - RotationXProperty

        /// <summary>
        /// X축 회전 속성
        /// </summary>
        public static readonly DependencyProperty RotationXProperty = DependencyProperty.Register
        (
            "RotationX",
            typeof(double),
            typeof(PanoramaViewer),
            new UIPropertyMetadata
            (
                0.0,
                (d, e) => ((PanoramaViewer)d).UpdateRotation()
            )
        );

        #endregion
        #region Y축 회전 속성 - RotationYProperty

        /// <summary>
        /// Y축 회전 속성
        /// </summary>
        public static readonly DependencyProperty RotationYProperty = DependencyProperty.Register
        (
            "RotationY",
            typeof(double),
            typeof(PanoramaViewer),
            new UIPropertyMetadata
            (
                0.0,
                (d, e) => ((PanoramaViewer)d).UpdateRotation()
            )
        );

        #endregion
        #region Z축 회전 속성 - RotationZProperty

        /// <summary>
        /// Z축 회전 속성
        /// </summary>
        public static readonly DependencyProperty RotationZProperty = DependencyProperty.Register
        (
            "RotationZ",
            typeof(double),
            typeof(PanoramaViewer),
            new UIPropertyMetadata
            (
                0.0,
                (d, e) => ((PanoramaViewer)d).UpdateRotation()
            )
        );

        #endregion
        #region 파노라마 이미지 속성 - PanoramaImageProperty

        /// <summary>
        /// 파노라마 이미지 속성
        /// </summary>
        public static readonly DependencyProperty PanoramaImageProperty = DependencyProperty.Register
        (
            "PanoramaImage",
            typeof(ImageSource),
            typeof(PanoramaViewer),
            new PropertyMetadata
            (
                null,
                new PropertyChangedCallback(PanoramaImagePropertyChangedCallback)
            )
        );

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// X축 벡터
        /// </summary>
        private static readonly Vector3D _xAxisVector = new Vector3D(1, 0, 0);

        /// <summary>
        /// Y축 벡터
        /// </summary>
        private static readonly Vector3D _yAxisVector = new Vector3D(0, 1, 0);

        /// <summary>
        /// Z축 벡터
        /// </summary>
        private static readonly Vector3D _zAxisVector = new Vector3D(0, 0, 1);

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Property
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 시야각 - FieldOfView

        /// <summary>
        /// 시야각
        /// </summary>
        public double FieldOfView
        {
            get
            {
                return (double)GetValue(FieldOfViewProperty);
            }
            set
            {
                SetValue(FieldOfViewProperty, value);
            }
        }

        #endregion
        #region X축 회전 - RotationX

        /// <summary>
        /// X축 회전
        /// </summary>
        public double RotationX
        {
            get
            {
                return (double)GetValue(RotationXProperty);
            }
            set
            {
                SetValue(RotationXProperty, value);
            }
        }

        #endregion
        #region Y축 회전 - RotationY

        /// <summary>
        /// Y축 회전
        /// </summary>
        public double RotationY
        {
            get
            {
                return (double)GetValue(RotationYProperty);
            }
            set
            {
                SetValue(RotationYProperty, value);
            }
        }

        #endregion
        #region Z축 회전 - RotationZ

        /// <summary>
        /// Z축 회전
        /// </summary>
        public double RotationZ
        {
            get
            {
                return (double)GetValue(RotationZProperty);
            }
            set
            {
                SetValue(RotationZProperty, value);
            }
        }

        #endregion
        #region 파노라마 이미지 - PanoramaImage

        /// <summary>
        /// 파노라마 이미지
        /// </summary>
        public ImageSource PanoramaImage
        {
            get
            {
                return (ImageSource)GetValue(PanoramaImageProperty);
            }
            set
            {
                SetValue(PanoramaImageProperty, value);
            }
        }

        #endregion

        #region 파노라마 객체 - PanoramaObject

        /// <summary>
        /// 파노라마 객체
        /// </summary>
        public ModelVisual3D PanoramaObject { get; set; }

        #endregion
        #region 파노라마 지오메트리 - PanoramaGeometry

        /// <summary>
        /// 파노라마 지오메트리
        /// </summary>
        public GeometryModel3D PanoramaGeometry { get; set; }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 파노라마 회전 - PanoramaRotation

        /// <summary>
        /// 파노라마 회전
        /// </summary>
        private QuaternionRotation3D PanoramaRotation { get; set; }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - PanoramaViewer()

        /// <summary>
        /// 생성자
        /// </summary>
        public PanoramaViewer()
        {
            InitializeViewer();
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Private

        #region 시야각 속성 변경시 콜백 처리하기 - FieldOfViewPropertyChangedCallback(sender, e)

        /// <summary>
        /// 시야각 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private static void FieldOfViewPropertyChangedCallback(object sender, DependencyPropertyChangedEventArgs e)
        {
            PanoramaViewer viewer = sender as PanoramaViewer;

            PerspectiveCamera camera = viewer.Camera as PerspectiveCamera;

            camera.FieldOfView = viewer.FieldOfView;
        }

        #endregion
        #region 파노라마 이미지 속성 변경시 콜백 처리하기 - PanoramaImagePropertyChangedCallback(sender, e)

        /// <summary>
        /// 파노라마 이미지 속성 변경시 콜백 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private static void PanoramaImagePropertyChangedCallback(object sender, DependencyPropertyChangedEventArgs e)
        {
            PanoramaViewer viewer = sender as PanoramaViewer;
 
            ImageBrush brush = new ImageBrush(viewer.PanoramaImage);

            viewer.PanoramaGeometry.BackMaterial = new DiffuseMaterial(brush);
        }

        #endregion

        ////////////////////////////////////////////////////////////////////////////////////////// Instance
        //////////////////////////////////////////////////////////////////////////////// Private

        #region 위치 구하기 - GetPosition(theta, y)

        /// <summary>
        /// 위치 구하기
        /// </summary>
        /// <param name="theta">세타</param>
        /// <param name="y">Y</param>
        /// <returns>위치</returns>
        private Point3D GetPosition(double theta, double y)
        {
            double r = Math.Sqrt(1 - y * y);
            double x = r * Math.Cos(theta);
            double z = r * Math.Sin(theta);

            return new Point3D(x, y, z);
        }

        #endregion
        #region 법선 벡터 구하기 - GetNormalVector(t, y)

        /// <summary>
        /// 법선 벡터 구하기
        /// </summary>
        /// <param name="t">T</param>
        /// <param name="y">Y</param>
        /// <returns>법선</returns>
        private Vector3D GetNormalVector(double t, double y)
        {
            return (Vector3D)GetPosition(t, y);
        }

        #endregion
        #region 텍스처 좌표 구하기 - GetTextureCoordinate(t, y)

        /// <summary>
        /// 텍스처 좌표 구하기
        /// </summary>
        /// <param name="t">T</param>
        /// <param name="y">Y</param>
        /// <returns>텍스처 좌표</returns>
        private Point GetTextureCoordinate(double t, double y)
        {
            Matrix matrix = new Matrix();

            matrix.Scale(1 / (2 * Math.PI), -0.5);

            Point point = new Point(t, y);

            point = point * matrix;

            return point;
        }

        #endregion
        #region 지오메트리 생성하기 - CreateGeometry()

        /// <summary>
        /// 지오메트리 생성하기
        /// </summary>
        /// <returns>지오메트리</returns>
        private Geometry3D CreateGeometry()
        {
            int thetaDivision = 64;
            int yDivision     = 64;

            double maximumTheta = (360.0 / 180.0) * Math.PI;
            double minimumY     = -1.0;
            double maximumY     = 1.0;

            double deltaTheta = maximumTheta / thetaDivision;
            double deltaY     = (maximumY - minimumY) / yDivision;

            MeshGeometry3D meshGeometry = new MeshGeometry3D();

            for(int i = 0; i <= yDivision; i++)
            {
                double y = minimumY + i * deltaY;

                for(int j = 0; j <= thetaDivision; j++)
                {
                    double t = j * deltaTheta;

                    meshGeometry.Positions.Add(GetPosition(t, y));

                    meshGeometry.Normals.Add(GetNormalVector(t, y));

                    meshGeometry.TextureCoordinates.Add(GetTextureCoordinate(t, y));
                }
            }

            for(int i = 0; i < yDivision; i++)
            {
                for(int j = 0; j < thetaDivision; j++)
                {
                    int x0 = j;
                    int x1 = (j + 1);
                    int y0 = i * (thetaDivision + 1);
                    int y1 = (i + 1) * (thetaDivision + 1);

                    meshGeometry.TriangleIndices.Add(x0 + y0);
                    meshGeometry.TriangleIndices.Add(x0 + y1);
                    meshGeometry.TriangleIndices.Add(x1 + y0);

                    meshGeometry.TriangleIndices.Add(x1 + y0);
                    meshGeometry.TriangleIndices.Add(x0 + y1);
                    meshGeometry.TriangleIndices.Add(x1 + y1);
                }
            }

            meshGeometry.Freeze();

            return meshGeometry;
        }

        #endregion
        #region 뷰어 초기화 하기 - InitializeViewer()

        /// <summary>
        /// 뷰어 초기화 하기
        /// </summary>
        private void InitializeViewer()
        {
            PerspectiveCamera perspectiveCamera = new PerspectiveCamera();

            perspectiveCamera.Position      = new Point3D(0, -0.0, 0);
            perspectiveCamera.UpDirection   = new Vector3D(0, 1, 0);
            perspectiveCamera.LookDirection = new Vector3D(0, 0, 1);
            perspectiveCamera.FieldOfView   = 80;

            Camera = perspectiveCamera;

            FieldOfView = 80;

            ModelVisual3D lightModelVisual3D = new ModelVisual3D();

            lightModelVisual3D.Content = new DirectionalLight(Colors.White, new Vector3D(0, 0, 1));

            Children.Add(lightModelVisual3D);

            PanoramaGeometry = new GeometryModel3D();

            PanoramaGeometry.Geometry = CreateGeometry();

            PanoramaObject = new ModelVisual3D();

            PanoramaObject.Content = PanoramaGeometry;

            RotateTransform3D rotateTransform = new RotateTransform3D();
            ScaleTransform3D  scaleTransform  = new ScaleTransform3D() { ScaleX = 1, ScaleY = 1.65, ScaleZ = 1 };

            Transform3DGroup transformGroup = new Transform3DGroup();

            PanoramaRotation = new QuaternionRotation3D();

            transformGroup.Children.Add(scaleTransform );
            transformGroup.Children.Add(rotateTransform);
            
            rotateTransform.Rotation = PanoramaRotation;

            PanoramaObject.Transform = transformGroup;

            Children.Add(PanoramaObject);
        }

        #endregion
        #region 회전 갱신하기 - UpdateRotation()

        /// <summary>
        /// 회전 갱신하기
        /// </summary>
        private void UpdateRotation()
        {
            Quaternion quaternionX = new Quaternion(_xAxisVector, RotationX);
            Quaternion quaternionY = new Quaternion(_yAxisVector, RotationY);
            Quaternion quaternionZ = new Quaternion(_zAxisVector, RotationZ);

            PanoramaRotation.Quaternion = quaternionX * quaternionY * quaternionZ;
        }

        #endregion
    }
}

 

728x90

 

▶ MainWindow.xaml

<Window x:Class="TestProject.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:TestProject"
    Width="800"
    Height="600"
    Title="파노라마 뷰 사용하기"
    FontFamily="나눔고딕코딩"
    FontSize="16">
    <Grid>
        <StackPanel
            HorizontalAlignment="Center"
            VerticalAlignment="Center">
            <Border
                Margin="10"
                BorderThickness="1"
                BorderBrush="Gray">
                <Grid>
                    <local:PanoramaViewer x:Name="viewer"
                        Height="350"
                        PanoramaImage="sample1.jpg" />
                </Grid>
            </Border>
            <StackPanel Margin="10 0 10 0">
                <StackPanel Orientation="Horizontal">
                    <TextBlock
                        VerticalAlignment="Center"
                        Text="시야각"/>
                    <Slider
                        VerticalAlignment="Center"
                        Margin="10 0 0 0"
                        Width="50"
                        Minimum="30"
                        Maximum="150"
                        Value="{Binding FieldOfView, ElementName=viewer}" />
                    <TextBlock
                        Margin="10 0 0 0"
                        VerticalAlignment="Center"
                        Text="X축 회전" />
                    <Slider
                        VerticalAlignment="Center"
                        Margin="10 0 0 0"
                        Width="90"
                        Minimum="-180"
                        Maximum="180"
                        Value="{Binding RotationX, ElementName=viewer}" />
                    <TextBlock
                        VerticalAlignment="Center"
                        Margin="10 0 0 0"
                        Text="Y축 회전" />
                    <Slider
                        VerticalAlignment="Center"
                        Margin="10 0 0 0"
                        Width="90"
                        Minimum="-180"
                        Maximum="180"
                        Value="{Binding RotationY, ElementName=viewer}" />
                    <TextBlock
                        VerticalAlignment="Center"
                        Margin="10 0 0 0"
                        Text="Z축 회전" />
                    <Slider
                        VerticalAlignment="Center"
                        Margin="10 0 0 0"
                        Width="90"
                        Minimum="-180"
                        Maximum="180"
                        Value="{Binding RotationZ, ElementName=viewer}" />
                </StackPanel>
            </StackPanel>
            <StackPanel
                Margin="10 10 10 0"
                HorizontalAlignment="Center"
                Orientation="Horizontal">
                <Button Name="button1"
                    Margin="10"
                    Padding="5"
                    Content="샘플 이미지 1" />
                <Button Name="button2"
                    Margin="10"
                    Padding="5"
                    Content="샘플 이미지 2" />
                <Button Name="button3"
                    Margin="10"
                    Padding="5"
                    Content="샘플 이미지 3" />
                <Button Name="button4"
                    Margin="10"
                    Padding="5"
                    Content="샘플 이미지 4" />
            </StackPanel>
        </StackPanel>
    </Grid>    
</Window>

 

300x250

 

▶ MainWindow.xaml.cs

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

namespace TestProject
{
    /// <summary>
    /// 메인 윈도우
    /// </summary>
    public partial class MainWindow : Window
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 회전 벡터
        /// </summary>
        private Vector rotationVector = new Vector();

        /// <summary>
        /// 드래그 시작 포인트
        /// </summary>
        private Point dragStartPoint;

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 생성자 - MainWindow()

        /// <summary>
        /// 생성자
        /// </summary>
        public MainWindow()
        {
            InitializeComponent();

            this.viewer.MouseLeftButtonDown += viewer_MouseLeftButtonDown;
            this.viewer.MouseMove           += viewer_MouseMove;
            this.button1.Click              += button_Click;
            this.button2.Click              += button_Click;
            this.button3.Click              += button_Click;
            this.button4.Click              += button_Click;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region 뷰어 마우스 왼쪽 버튼 DOWN 처리하기 - viewer_MouseLeftButtonDown(sender, e)

        /// <summary>
        /// 뷰어 마우스 왼쪽 버튼 DOWN 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void viewer_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            dragStartPoint = e.GetPosition(this.viewer);

            this.rotationVector.X = this.viewer.RotationX;
            this.rotationVector.Y = this.viewer.RotationY;
        }

        #endregion
        #region 뷰어 마우스 이동시 처리하기 - viewer_MouseMove(sender, e)

        /// <summary>
        /// 뷰어 마우스 이동시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void viewer_MouseMove(object sender, MouseEventArgs e)
        {
            if(e.LeftButton == MouseButtonState.Released)
            {
                return;
            }

            Vector offsetVector = Point.Subtract(e.GetPosition(this.viewer) , this.dragStartPoint) * 0.25;

            this.viewer.RotationY = this.rotationVector.Y + offsetVector.X;
            this.viewer.RotationX = this.rotationVector.X + offsetVector.Y;
        }

        #endregion
        #region 버튼 클릭시 처리하기 - button_Click(sender, e)

        /// <summary>
        /// 버튼 클릭시 처리하기
        /// </summary>
        /// <param name="sender">이벤트 발생자</param>
        /// <param name="e">이벤트 인자</param>
        private void button_Click(object sender, RoutedEventArgs e)
        {
            Button button = sender as Button;

            string imageFileName = null;

            switch(button.Name)
            {
                case "button1" : imageFileName = "sample1.jpg"; break;
                case "button2" : imageFileName = "sample2.jpg"; break;
                case "button3" : imageFileName = "sample3.jpg"; break;
                case "button4" : imageFileName = "sample4.jpg"; break;
            }

            Uri uri = new Uri($"pack://application:,,,/{imageFileName}");
            
            BitmapImage bitmapImage = new BitmapImage(uri);

            this.viewer.PanoramaImage = bitmapImage;
        }

        #endregion
    }
}
728x90
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요