첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.
유용한 소스 코드가 있으면 icodebroker@naver.com으로 보내주시면 감사합니다.
블로그 자료는 자유롭게 사용하세요.

■ 3차원 와이어 프레임 그리기

------------------------------------------------------------------------------------------------------------------------


TestProject.zip


MeshExtensions.cs

 

 

using System;

using System.Collections.Generic;

using System.Windows.Media.Media3D;

 

namespace TestProject

{

    /// <summary>

    /// 메쉬 확장

    /// </summary>

    public static class MeshExtension

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region 와이어 프레임 만들기 - ToWireFrame(meshGeometry3D, thickness)

 

        /// <summary>

        /// 와이어 프레임 만들기

        /// </summary>

        /// <param name="meshGeometry3D">3차원 메쉬 기하</param>

        /// <param name="thickness">두께</param>

        /// <returns>3차원 메쉬 기하</returns>

        public static MeshGeometry3D ToWireFrame(this MeshGeometry3D meshGeometry3D, double thickness)

        {

            Dictionary<int, int> drawDictionary = new Dictionary<int, int>();

 

            MeshGeometry3D wireFrameMeshGeometry3D = new MeshGeometry3D();

 

            for(int i = 0; i < meshGeometry3D.TriangleIndices.Count; i += 3)

            {

                int index1 = meshGeometry3D.TriangleIndices[i    ];

                int index2 = meshGeometry3D.TriangleIndices[i + 1];

                int index3 = meshGeometry3D.TriangleIndices[i + 2];

 

                AddTriangleSegment(meshGeometry3D, wireFrameMeshGeometry3D, drawDictionary, index1, index2, thickness);

                AddTriangleSegment(meshGeometry3D, wireFrameMeshGeometry3D, drawDictionary, index2, index3, thickness);

                AddTriangleSegment(meshGeometry3D, wireFrameMeshGeometry3D, drawDictionary, index3, index1, thickness);

            }

 

            return wireFrameMeshGeometry3D;

        }

 

        #endregion

 

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

 

        #region 스케일 벡터 구하기 - GetScaleVector(vector3D, length)

 

        /// <summary>

        /// 스케일 벡터 구하기

        /// </summary>

        /// <param name="vector3D">3차원 벡터</param>

        /// <param name="length">길이</param>

        /// <returns>스케일 벡터</returns>

        public static Vector3D GetScaleVector(Vector3D vector3D, double length)

        {

            double scale = length / vector3D.Length;

 

            return new Vector3D

            (

                vector3D.X * scale,

                vector3D.Y * scale,

                vector3D.Z * scale

            );

        }

 

        #endregion

        #region 삼각형 추가하기 - AddTriangle(meshGeometry3D, point3D1, point3D2, point3D3)

 

        /// <summary>

        /// 삼각형 추가하기

        /// </summary>

        /// <param name="meshGeometry3D">3차원 메쉬 기하</param>

        /// <param name="point3D1">3차원 포인트 1</param>

        /// <param name="point3D2">3차원 포인트 2</param>

        /// <param name="point3D3">3차원 포인트 3</param>

        private static void AddTriangle(MeshGeometry3D meshGeometry3D, Point3D point3D1, Point3D point3D2, Point3D point3D3)

        {

            int index = meshGeometry3D.Positions.Count;

 

            meshGeometry3D.Positions.Add(point3D1);

            meshGeometry3D.Positions.Add(point3D2);

            meshGeometry3D.Positions.Add(point3D3);

 

            meshGeometry3D.TriangleIndices.Add(index++);

            meshGeometry3D.TriangleIndices.Add(index++);

            meshGeometry3D.TriangleIndices.Add(index  );

        }

 

        #endregion

        #region 세그먼트 추가하기 - AddSegment(meshGeometry3D, point3D1, point3D2, upVector3D, thickness, extend)

 

        /// <summary>

        /// 세그먼트 추가하기

        /// </summary>

        /// <param name="meshGeometry3D">3차원 메쉬 기하</param>

        /// <param name="point3D1">3차원 포인트 1</param>

        /// <param name="point3D2">3차원 포인트 2</param>

        /// <param name="upVector3D">UP 3차원 벡터</param>

        /// <param name="thickness">두께</param>

        /// <param name="extend">확장 여부</param>

        public static void AddSegment(MeshGeometry3D meshGeometry3D, Point3D point3D1, Point3D point3D2, Vector3D upVector3D,

            double thickness, bool extend)

        {

            Vector3D vector = point3D2 - point3D1;

 

            if(extend)

            {

                Vector3D scaleVector = GetScaleVector(vector, thickness / 2.0);

 

                point3D1 -= scaleVector;

                point3D2 += scaleVector;

            }

 

            Vector3D scaleVector1 = GetScaleVector(upVector3D, thickness / 2.0);

            Vector3D scaleVector2 = Vector3D.CrossProduct(vector, scaleVector1);

 

            scaleVector2 = GetScaleVector(scaleVector2, thickness / 2.0);

 

            Point3D p1pp = point3D1 + scaleVector1 + scaleVector2;

            Point3D p1mp = point3D1 - scaleVector1 + scaleVector2;

            Point3D p1pm = point3D1 + scaleVector1 - scaleVector2;

            Point3D p1mm = point3D1 - scaleVector1 - scaleVector2;

            Point3D p2pp = point3D2 + scaleVector1 + scaleVector2;

            Point3D p2mp = point3D2 - scaleVector1 + scaleVector2;

            Point3D p2pm = point3D2 + scaleVector1 - scaleVector2;

            Point3D p2mm = point3D2 - scaleVector1 - scaleVector2;

 

            AddTriangle(meshGeometry3D, p1pp, p1mp, p2mp);

            AddTriangle(meshGeometry3D, p1pp, p2mp, p2pp);

 

            AddTriangle(meshGeometry3D, p1pp, p2pp, p2pm);

            AddTriangle(meshGeometry3D, p1pp, p2pm, p1pm);

 

            AddTriangle(meshGeometry3D, p1pm, p2pm, p2mm);

            AddTriangle(meshGeometry3D, p1pm, p2mm, p1mm);

 

            AddTriangle(meshGeometry3D, p1mm, p2mm, p2mp);

            AddTriangle(meshGeometry3D, p1mm, p2mp, p1mp);

 

            // Ends.

            AddTriangle(meshGeometry3D, p1pp, p1pm, p1mm);

            AddTriangle(meshGeometry3D, p1pp, p1mm, p1mp);

 

            AddTriangle(meshGeometry3D, p2pp, p2mp, p2mm);

            AddTriangle(meshGeometry3D, p2pp, p2mm, p2pm);

        }

 

        #endregion

        #region 세그먼트 추가하기 - AddSegment(meshGeometry3D, point3D1, point3D2, thickness, extend)

 

        /// <summary>

        /// 세그먼트 추가하기

        /// </summary>

        /// <param name="meshGeometry3D">3차원 메쉬 기하</param>

        /// <param name="point3D1">3차원 포인트 1</param>

        /// <param name="point3D2">3차원 포인트 2</param>

        /// <param name="thickness">두께</param>

        /// <param name="extend">확장 여부</param>

        public static void AddSegment(MeshGeometry3D meshGeometry3D, Point3D point3D1, Point3D point3D2, double thickness, bool extend)

        {

            Vector3D upVector3D = new Vector3D(0, 1, 0);

 

            Vector3D vector3D = point3D2 - point3D1;

 

            vector3D.Normalize();

 

            if(Math.Abs(Vector3D.DotProduct(upVector3D, vector3D)) > 0.9)

            {

                upVector3D = new Vector3D(1, 0, 0);

            }

 

            AddSegment(meshGeometry3D, point3D1, point3D2, upVector3D, thickness, extend);

        }

 

        #endregion

        #region 세그먼트 추가하기 - AddSegment(meshGeometry3D, point3D1, point3D2, thickness)

 

        /// <summary>

        /// 세그먼트 추가하기

        /// </summary>

        /// <param name="meshGeometry3D">3차원 메쉬 기하</param>

        /// <param name="point3D1">3차원 포인트 1</param>

        /// <param name="point3D2">3차원 포인트 2</param>

        /// <param name="thickness">두께</param>

        public static void AddSegment(MeshGeometry3D meshGeometry3D, Point3D point3D1, Point3D point3D2, double thickness)

        {

            AddSegment(meshGeometry3D, point3D1, point3D2, thickness, false);

        }

 

        #endregion

        #region 삼각형 세그먼트 추가하기 - AddTriangleSegment(meshGeometry3D, wirefFamemeshGeometry3D, drawDictionary, index1, index2,

            thickness)

 

        /// <summary>

        /// 삼각형 세그먼트 추가하기

        /// </summary>

        /// <param name="meshGeometry3D">3차원 메쉬 기하</param>

        /// <param name="wirefFamemeshGeometry3D">와이어 프레임 3차원 메쉬 기하</param>

        /// <param name="drawDictionary">그리기 딕셔너리</param>

        /// <param name="index1">인덱스 1</param>

        /// <param name="index2">인덱스 2</param>

        /// <param name="thickness">두께</param>

        private static void AddTriangleSegment(MeshGeometry3D meshGeometry3D, MeshGeometry3D wirefFamemeshGeometry3D,

            Dictionary<int, int> drawDictionary, int index1, int index2, double thickness)

        {

            if(index1 > index2)

            {

                int temporary = index1;

 

                index1 = index2;

                index2 = temporary;

            }

 

            int segmentID = index1 * meshGeometry3D.Positions.Count + index2;

 

            if(drawDictionary.ContainsKey(segmentID))

            {

                return;

            }

 

            drawDictionary.Add(segmentID, segmentID);

 

            AddSegment

            (

                wirefFamemeshGeometry3D,

                meshGeometry3D.Positions[index1],

                meshGeometry3D.Positions[index2],

                thickness

            );

        }

 

        #endregion

    }

}

 

 

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"

    Width="800"

    Height="600"

    Title="3차원 와이어 프레임 그리기"

    FontFamily="나눔고딕코딩"

    FontSize="16"

    Loaded="Window_Loaded"

    KeyDown="Window_KeyDown">

    <Grid>

        <Viewport3D Name="viewport3D" />

        <StackPanel

            HorizontalAlignment="Left"

            VerticalAlignment="Top"

            Margin="0"

            Width="Auto"

            Height="Auto"

            Orientation="Horizontal">

            <CheckBox Name="surfaceCheckBox"

                Margin="10"

                HorizontalAlignment="Left"

                VerticalAlignment="Top"

                Width="120"

                Height="16"

                IsChecked="True"

                Click="checkBox_Click">

                표면

            </CheckBox>

            <CheckBox Name="wireFrameCheckBox"

                Margin="10"

                HorizontalAlignment="Left"

                VerticalAlignment="Top"

                Width="120"

                Height="16"

                Click="checkBox_Click"

                IsChecked="True">

                와이어프레임

            </CheckBox>

        </StackPanel>

    </Grid>

</Window>

 

 

MainWindow.xaml.cs

 

 

using System;

using System.Collections.Generic;

using System.Windows;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Media3D;

 

namespace TestProject

{

    /// <summary>

    /// 메인 윈도우

    /// </summary>

    public partial class MainWindow : Window

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

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

 

        #region Field

 

        /// <summary>

        /// 3차원 모델 그룹

        /// </summary>

        private Model3DGroup model3DGroup = new Model3DGroup();

 

        /// <summary>

        /// 카메라

        /// </summary>

        private PerspectiveCamera camera;

 

        /// <summary>

        /// 카메라 파이

        /// </summary>

        private double cameraPhi = Math.PI / 6.0;

 

        /// <summary>

        /// 카메라 세타

        /// </summary>

        private double cameraTheta = Math.PI / 6.0;

 

        /// <summary>

        /// 카메라 R

        /// </summary>

        private double cameraR = 3.0;

 

        /// <summary>

        /// 카메라 델타 파이

        /// </summary>

        private const double CAMERA_DELTA_PHI = 0.1;

 

        /// <summary>

        /// 카메라 델타 세타

        /// </summary>

        private const double CAMERA_DELTA_THETA = 0.1;

 

        /// <summary>

        /// 카메라 델타 R

        /// </summary>

        private const double CAMERA_DELTA_R = 0.1;

 

        /// <summary>

        /// 표면 3차원 기하 모델

        /// </summary>

        private GeometryModel3D surfaceGeometryModel3D;

 

        /// <summary>

        /// 와이어 프레임 3차원 기하 모델

        /// </summary>

        private GeometryModel3D wireFrameGeometryModel3D;

 

        /// <summary>

        /// 3차원 포인트 딕셔너리

        /// </summary>

        private Dictionary<Point3D, int> point3DDictionary = new Dictionary<Point3D, int>();

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - MainWindow()

 

        /// <summary>

        /// 생성자

        /// </summary>

        public MainWindow()

        {

            InitializeComponent();

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

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

        //////////////////////////////////////////////////////////////////////////////// Event

 

        #region 윈도우 로드시 처리하기 - Window_Loaded(sender, e)

 

        /// <summary>

        /// 윈도우 로드시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void Window_Loaded(object sender, RoutedEventArgs e)

        {

            this.camera = new PerspectiveCamera();

 

            this.camera.FieldOfView = 60;

 

            this.viewport3D.Camera = this.camera;

 

            SetCameraPosition();

 

            DefineLight();

 

            DefineModel(this.model3DGroup);

 

            ModelVisual3D modelVisual3D = new ModelVisual3D();

 

            modelVisual3D.Content = this.model3DGroup;

 

            this.viewport3D.Children.Add(modelVisual3D);

        }

 

        #endregion

        #region 윈도우 키 DOWN 처리하기 - Window_KeyDown(sender, e)

 

        /// <summary>

        /// 윈도우 키 DOWN 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void Window_KeyDown(object sender, KeyEventArgs e)

        {

            switch(e.Key)

            {

                case Key.Up :

 

                    this.cameraPhi += CAMERA_DELTA_PHI;

 

                    if(this.cameraPhi > Math.PI / 2.0)

                    {

                        this.cameraPhi = Math.PI / 2.0;

                    }

 

                    break;

 

                case Key.Down :

 

                    this.cameraPhi -= CAMERA_DELTA_PHI;

 

                    if(this.cameraPhi < -Math.PI / 2.0)

                    {

                        this.cameraPhi = -Math.PI / 2.0;

                    }

 

                    break;

 

                case Key.Left :

 

                    this.cameraTheta += CAMERA_DELTA_THETA;

 

                    break;

 

                case Key.Right :

 

                    this.cameraTheta -= CAMERA_DELTA_THETA;

 

                    break;

 

                case Key.Add     :

                case Key.OemPlus :

 

                    this.cameraR -= CAMERA_DELTA_R;

 

                    if(this.cameraR < CAMERA_DELTA_R)

                    {

                        this.cameraR = CAMERA_DELTA_R;

                    }

 

                    break;

 

                case Key.Subtract :

                case Key.OemMinus :

 

                    this.cameraR += CAMERA_DELTA_R;

 

                    break;

            }

 

            SetCameraPosition();

        }

 

        #endregion

        #region 체크 박스 클릭시 처리하기 - checkBox_Click(sender, e)

 

        /// <summary>

        /// 체크 박스 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void checkBox_Click(object sender, RoutedEventArgs e)

        {

            for(int i = this.model3DGroup.Children.Count - 1; i >= 0; i--)

            {

                if(this.model3DGroup.Children[i] is GeometryModel3D)

                {

                    this.model3DGroup.Children.RemoveAt(i);

                }

            }

 

            if((this.surfaceGeometryModel3D != null) && ((bool)this.surfaceCheckBox.IsChecked))

            {

                this.model3DGroup.Children.Add(this.surfaceGeometryModel3D);

            }

 

            if((this.wireFrameGeometryModel3D != null) && ((bool)this.wireFrameCheckBox.IsChecked))

            {

                this.model3DGroup.Children.Add(this.wireFrameGeometryModel3D);

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////// Function

 

        #region 카메라 위치 설정하기 - SetCameraPosition()

 

        /// <summary>

        /// 카메라 위치 설정하기

        /// </summary>

        private void SetCameraPosition()

        {

            double y   = this.cameraR * Math.Sin(this.cameraPhi);

            double hyp = this.cameraR * Math.Cos(this.cameraPhi);

            double x   = hyp * Math.Cos(this.cameraTheta);

            double z   = hyp * Math.Sin(this.cameraTheta);

 

            this.camera.Position = new Point3D(x, y, z);

 

            this.camera.LookDirection = new Vector3D(-x, -y, -z);

 

            this.camera.UpDirection = new Vector3D(0, 1, 0);

        }

 

        #endregion

        #region 조명 정의하기 - DefineLight()

 

        /// <summary>

        /// 조명 정의하기

        /// </summary>

        private void DefineLight()

        {

            AmbientLight ambientLight = new AmbientLight(Colors.Gray);

 

            DirectionalLight directionalLight = new DirectionalLight(Colors.Gray, new Vector3D(-1.0, -3.0, -2.0));

 

            this.model3DGroup.Children.Add(ambientLight);

            this.model3DGroup.Children.Add(directionalLight);

        }

 

        #endregion

        #region Y 좌표 구하기 - GetY(x, z)

 

        /// <summary>

        /// Y 좌표 구하기

        /// </summary>

        /// <param name="x">X 좌표</param>

        /// <param name="z">Z 좌표</param>

        /// <returns>Y 좌표</returns>

        private double GetY(double x, double z)

        {

            const double TWO_PI = 2 * 3.14159265;

 

            double r2    = x * x + z * z;

            double r     = Math.Sqrt(r2);

            double theta = Math.Atan2(z, x);

 

            return Math.Exp(-r2) * Math.Sin(TWO_PI * r) * Math.Cos(3 * theta);

        }

 

        #endregion

        #region 포인트 추가하기 - AddPoint(point3DCollection, point3D)

 

        /// <summary>

        /// 포인트 추가하기

        /// </summary>

        /// <param name="point3DCollection">3차원 포인트 컬렉션</param>

        /// <param name="point3D">3차원 포인트</param>

        /// <returns>포인트 인덱스</returns>

        private int AddPoint(Point3DCollection point3DCollection, Point3D point3D)

        {

            if(this.point3DDictionary.ContainsKey(point3D))

            {

                return this.point3DDictionary[point3D];

            }

 

            point3DCollection.Add(point3D);

 

            this.point3DDictionary.Add(point3D, point3DCollection.Count - 1);

 

            return point3DCollection.Count - 1;

        }

 

        #endregion

        #region 삼각형 추가하기 - AddTriangle(meshGeometry3D, point3D1, point3D2, point3D3)

 

        /// <summary>

        /// 삼각형 추가하기

        /// </summary>

        /// <param name="meshGeometry3D">3차원 메쉬 기하</param>

        /// <param name="point3D1">3차원 포인트 1</param>

        /// <param name="point3D2">3차원 포인트 2</param>

        /// <param name="point3D3">3차원 포인트 3</param>

        private void AddTriangle(MeshGeometry3D meshGeometry3D, Point3D point3D1, Point3D point3D2, Point3D point3D3)

        {

            int index1 = AddPoint(meshGeometry3D.Positions, point3D1);

            int index2 = AddPoint(meshGeometry3D.Positions, point3D2);

            int index3 = AddPoint(meshGeometry3D.Positions, point3D3);

 

            meshGeometry3D.TriangleIndices.Add(index1);

            meshGeometry3D.TriangleIndices.Add(index2);

            meshGeometry3D.TriangleIndices.Add(index3);

        }

 

        #endregion

        #region 모델 정의하기 - DefineModel(model3DGroup)

 

        /// <summary>

        /// 모델 정의하기

        /// </summary>

        /// <param name="model3DGroup">3차원 모델 그룹</param>

        private void DefineModel(Model3DGroup model3DGroup)

        {

            MeshGeometry3D meshGeometry3D = new MeshGeometry3D();

 

            const double xMinimum = -1.5;

            const double xMaximum = 1.5;

            const double deltaX   = 0.3;

            const double zMinimum = -1.5;

            const double zMaximum = 1.5;

            const double deltaZ   = 0.3;

 

            for(double x = xMinimum; x <= xMaximum - deltaX; x += deltaX)

            {

                for(double z = zMinimum; z <= zMaximum - deltaZ; z += deltaX)

                {

                    Point3D p00 = new Point3D(x         , GetY(x         , z         ), z         );

                    Point3D p10 = new Point3D(x + deltaX, GetY(x + deltaX, z         ), z         );

                    Point3D p01 = new Point3D(x         , GetY(x         , z + deltaZ), z + deltaZ);

                    Point3D p11 = new Point3D(x + deltaX, GetY(x + deltaX, z + deltaZ), z + deltaZ);

 

                    AddTriangle(meshGeometry3D, p00, p01, p11);

                    AddTriangle(meshGeometry3D, p00, p11, p10);

                }

            }

 

            DiffuseMaterial surfaceDiffuseMaterial = new DiffuseMaterial(Brushes.LightGreen);

 

            this.surfaceGeometryModel3D = new GeometryModel3D(meshGeometry3D, surfaceDiffuseMaterial);

 

            this.surfaceGeometryModel3D.BackMaterial = surfaceDiffuseMaterial;

 

            model3DGroup.Children.Add(this.surfaceGeometryModel3D);

 

            MeshGeometry3D wireframeMeshGeometry3D = meshGeometry3D.ToWireFrame(0.01);

 

            DiffuseMaterial wireFrameDiffuseMaterial = new DiffuseMaterial(Brushes.Red);

 

            this.wireFrameGeometryModel3D = new GeometryModel3D(wireframeMeshGeometry3D, wireFrameDiffuseMaterial);

 

            model3DGroup.Children.Add(this.wireFrameGeometryModel3D);

        }

 

        #endregion

    }

}

 

------------------------------------------------------------------------------------------------------------------------

Posted by 사용자 icodebroker

댓글을 달아 주세요