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

728x90
반응형

TestProject.zip
다운로드

▶ WriteableBitmapHelper.cs

using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace TestProject
{
    /// <summary>
    /// 쓰기 가능 비트맵 헬퍼
    /// </summary>
    public class WriteableBitmapHelper
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Field
        ////////////////////////////////////////////////////////////////////////////////////////// Private

        #region Field

        /// <summary>
        /// 너비
        /// </summary>
        private int width;
        
        /// <summary>
        /// 높이
        /// </summary>
        private int height;

        /// <summary>
        /// 픽셀 배열
        /// </summary>
        private byte[] pixelArray;

        /// <summary>
        /// 스트라이드
        /// </summary>
        private int stride;

        #endregion

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

        #region 생성자 - WriteableBitmapHelper(width, height)

        /// <summary>
        /// 생성자
        /// </summary>
        /// <param name="width">너비</param>
        /// <param name="height">높이</param>
        public WriteableBitmapHelper(int width, int height)
        {
            this.width  = width;
            this.height = height;

            this.pixelArray = new byte[width * height * 4];

            this.stride = width * 4;
        }

        #endregion

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Public

        #region 픽셀 구하기 - GetPixel(x, y, red, green, blue, alpha)

        /// <summary>
        /// 픽셀 구하기
        /// </summary>
        /// <param name="x">X 좌표</param>
        /// <param name="y">Y 좌표</param>
        /// <param name="red">빨강색</param>
        /// <param name="green">녹색</param>
        /// <param name="blue">파랑색</param>
        /// <param name="alpha">투명도</param>
        public void GetPixel(int x, int y, out byte red, out byte green, out byte blue, out byte alpha)
        {
            int index = y * this.stride + x * 4;

            blue  = this.pixelArray[index++];
            green = this.pixelArray[index++];
            red   = this.pixelArray[index++];
            alpha = this.pixelArray[index  ];
        }

        #endregion
        #region 파랑색 구하기 - GetBlue(x, y)

        /// <summary>
        /// 파랑색 구하기
        /// </summary>
        /// <param name="x">X 좌표</param>
        /// <param name="y">Y 좌표</param>
        /// <returns>파랑색</returns>
        public byte GetBlue(int x, int y)
        {
            return this.pixelArray[y * this.stride + x * 4];
        }

        #endregion
        #region 녹색 구하기 - GetGreen(x, y)

        /// <summary>
        /// 녹색 구하기
        /// </summary>
        /// <param name="x">X 좌표</param>
        /// <param name="y">Y 좌표</param>
        /// <returns>녹색</returns>
        public byte GetGreen(int x, int y)
        {
            return this.pixelArray[y * this.stride + x * 4 + 1];
        }

        #endregion
        #region 빨강색 구하기 - GetRed(x, y)

        /// <summary>
        /// 빨강색 구하기
        /// </summary>
        /// <param name="x">X 좌표</param>
        /// <param name="y">Y 좌표</param>
        /// <returns>빨강색</returns>
        public byte GetRed(int x, int y)
        {
            return this.pixelArray[y * this.stride + x * 4 + 2];
        }

        #endregion
        #region 투명도 구하기 - GetAlpha(x, y)

        /// <summary>
        /// 투명도 구하기
        /// </summary>
        /// <param name="x">X 좌표</param>
        /// <param name="y">Y 좌표</param>
        /// <returns>투명도</returns>
        public byte GetAlpha(int x, int y)
        {
            return this.pixelArray[y * this.stride + x * 4 + 3];
        }

        #endregion
        #region 픽셀 설정하기 - SetPixel(x, y, red, green, blue, alpha)

        /// <summary>
        /// 픽셀 설정하기
        /// </summary>
        /// <param name="x">X 좌표</param>
        /// <param name="y">Y 좌표</param>
        /// <param name="red">빨강색</param>
        /// <param name="green">녹색</param>
        /// <param name="blue">파랑색</param>
        /// <param name="alpha">투명도</param>
        public void SetPixel(int x, int y, byte red, byte green, byte blue, byte alpha)
        {
            int index = y * this.stride + x * 4;

            this.pixelArray[index++] = blue;
            this.pixelArray[index++] = green;
            this.pixelArray[index++] = red;
            this.pixelArray[index++] = alpha;
        }

        #endregion
        #region 파랑색 설정하기 - SetBlue(x, y, blue)

        /// <summary>
        /// 파랑색 설정하기
        /// </summary>
        /// <param name="x">X 좌표</param>
        /// <param name="y">Y 좌표</param>
        /// <param name="blue">파랑색</param>
        public void SetBlue(int x, int y, byte blue)
        {
            this.pixelArray[y * this.stride + x * 4] = blue;
        }

        #endregion
        #region 녹색 설정하기 - SetGreen(x, y, green)

        /// <summary>
        /// 녹색 설정하기
        /// </summary>
        /// <param name="x">X 좌표</param>
        /// <param name="y">Y 좌표</param>
        /// <param name="green">녹색</param>
        public void SetGreen(int x, int y, byte green)
        {
            this.pixelArray[y * this.stride + x * 4 + 1] = green;
        }

        #endregion
        #region 빨강색 설정하기 - SetRed(x, y, red)

        /// <summary>
        /// 빨강색 설정하기
        /// </summary>
        /// <param name="x">X 좌표</param>
        /// <param name="y">Y 좌표</param>
        /// <param name="red">빨강색</param>
        public void SetRed(int x, int y, byte red)
        {
            this.pixelArray[y * this.stride + x * 4 + 2] = red;
        }

        #endregion
        #region 투명도 설정하기 - SetAlpha(x, y, alpha)

        /// <summary>
        /// 투명도 설정하기
        /// </summary>
        /// <param name="x">X 좌표</param>
        /// <param name="y">Y 좌표</param>
        /// <param name="alpha">투명도</param>
        public void SetAlpha(int x, int y, byte alpha)
        {
            this.pixelArray[y * this.stride + x * 4 + 3] = alpha;
        }

        #endregion
        #region 색상 설정하기 - SetColor(red, green, blue)

        /// <summary>
        /// 색상 설정하기
        /// </summary>
        /// <param name="red">빨강색</param>
        /// <param name="green">녹색</param>
        /// <param name="blue">파랑색</param>
        public void SetColor(byte red, byte green, byte blue)
        {
            SetColor(red, green, blue, 255);
        }

        #endregion
        #region 색상 설정하기 - SetColor(red, green, blue, alpha)

        /// <summary>
        /// 색상 설정하기
        /// </summary>
        /// <param name="red">빨강색</param>
        /// <param name="green">녹색</param>
        /// <param name="blue">파랑색</param>
        /// <param name="alpha">투명도</param>
        public void SetColor(byte red, byte green, byte blue, byte alpha)
        {
            int byteCount = this.width * this.height * 4;
            int index     = 0;

            while(index < byteCount)
            {
                this.pixelArray[index++] = blue;
                this.pixelArray[index++] = green;
                this.pixelArray[index++] = red;
                this.pixelArray[index++] = alpha;
            }
        }

        #endregion
        #region 비트맵 구하기 - GetBitmap(dpiX, dpiY)

        /// <summary>
        /// 비트맵 구하기
        /// </summary>
        /// <param name="dpiX">X DPI</param>
        /// <param name="dpiY">Y DPI</param>
        /// <returns>쓰기 가능 비트맵</returns>
        public WriteableBitmap GetBitmap(double dpiX, double dpiY)
        {
            WriteableBitmap bitmap = new WriteableBitmap
            (
                this.width,
                this.height,
                dpiX,
                dpiY,
                PixelFormats.Bgra32,
                null
            );

            Int32Rect rectangle = new Int32Rect(0, 0, this.width, this.height);

            bitmap.WritePixels(rectangle, this.pixelArray, this.stride, 0);

            return bitmap;
        }

        #endregion
    }
}

 

▶ WriteableBitmapExtention.cs

using System.IO;
using System.Windows.Media.Imaging;

namespace TestProject
{
    /// <summary>
    /// 쓰기 가능 비트맵 확장
    /// </summary>
    public static class WriteableBitmapExtention
    {
        //////////////////////////////////////////////////////////////////////////////////////////////////// Method
        ////////////////////////////////////////////////////////////////////////////////////////// Static
        //////////////////////////////////////////////////////////////////////////////// Public

        #region 저장하기 - Save(bitmap, filePath)

        /// <summary>
        /// 저장하기
        /// </summary>
        /// <param name="bitmap">비트맵</param>
        /// <param name="filePath">파일 경로</param>
        public static void Save(this WriteableBitmap bitmap, string filePath)
        {
            using(FileStream stream = new FileStream(filePath, FileMode.Create))
            {
                PngBitmapEncoder encoder = new PngBitmapEncoder();

                encoder.Frames.Add(BitmapFrame.Create(bitmap));

                encoder.Save(stream);
            }
        }

        #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차원 고도 맵 그리기"
    Loaded="Window_Loaded"
    KeyDown="Window_KeyDown">
    <Grid>
        <Viewport3D Name="viewport3D" />
    </Grid>
</Window>

 

▶ MainWindow.xaml.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
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>
        /// X 최소값
        /// </summary>
        private const double X_MINIMUM = -1.5;

        /// <summary>
        /// X 최대값
        /// </summary>
        private const double X_MAXIMUM = 1.5;

        /// <summary>
        /// 델타 X
        /// </summary>
        private const double DELTA_X = 0.03;

        /// <summary>
        /// Z 최소값
        /// </summary>
        private const double Z_MINIMUM = -1.5;

        /// <summary>
        /// Z 최대값
        /// </summary>
        private const double Z_MAXIMUM = 1.5;

        /// <summary>
        /// 델타 Z
        /// </summary>
        private const double DELTA_Z = 0.03;

        /// <summary>
        /// 텍스처 X 스케일
        /// </summary>
        private const double TEXTURE_X_SCALE = (X_MAXIMUM - X_MINIMUM);

        /// <summary>
        /// 텍스처 Z 스케일
        /// </summary>
        private const double TEXTURE_Z_SCALE = (Z_MAXIMUM - Z_MINIMUM);

        /// <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();

            CreateAltitudeMap();

            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

        //////////////////////////////////////////////////////////////////////////////// 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 무지개 색상 맵 설정하기 - SetRainbowColorMap(value, minimumValue, maximumValue, red, green, blue)

        /// <summary>
        /// 무지개 색상 맵 설정하기
        /// </summary>
        /// <param name="value">값</param>
        /// <param name="minimumValue">최소값</param>
        /// <param name="maximumValue">최대값</param>
        /// <param name="red">빨강색</param>
        /// <param name="green">녹색</param>
        /// <param name="blue">파랑색</param>
        private void SetRainbowColorMap(double value, double minimumValue, double maximumValue, out byte red, out byte green, out byte blue)
        {
            int mapValue = (int)(1023 * (value - minimumValue) / (maximumValue - minimumValue));

            if(mapValue < 256)
            {
                red   = 255;
                green = (byte)mapValue;
                blue  = 0;
            }
            else if(mapValue < 512)
            {
                mapValue -= 256;

                red   = (byte)(255 - mapValue);
                green = 255;
                blue  = 0;
            }
            else if(mapValue < 768)
            {
                mapValue -= 512;

                red   = 0;
                green = 255;
                blue  = (byte)mapValue;
            }
            else
            {
                mapValue -= 768;

                red   = 0;
                green = (byte)(255 - mapValue);
                blue  = 255;
            }
        }

        #endregion
        #region 고도 맵 생성하기 - CreateAltitudeMap()

        /// <summary>
        /// 고도 맵 생성하기
        /// </summary>
        private void CreateAltitudeMap()
        {
            const int X_WIDTH = 512;
            const int Z_WIDTH = 512;

            const double deltaX = (X_MAXIMUM - X_MINIMUM) / X_WIDTH;
            const double deltaZ = (Z_MAXIMUM - Z_MINIMUM) / Z_WIDTH;

            double[,] valueArray = new double[X_WIDTH, Z_WIDTH];

            for(int xIndex = 0; xIndex < X_WIDTH; xIndex++)
            {
                double x = X_MINIMUM + xIndex * deltaX;

                for(int zIndex = 0; zIndex < Z_WIDTH; zIndex++)
                {
                    double z = Z_MINIMUM + zIndex * deltaZ;

                    valueArray[xIndex, zIndex] = GetY(x, z);
                }
            }

            var valueEnumerable = from double value in valueArray
                                  select value;

            double yMinimum = valueEnumerable.Min();
            double yMaximum = valueEnumerable.Max();

            WriteableBitmapHelper bitmapPixelMaker = new WriteableBitmapHelper(X_WIDTH, Z_WIDTH);

            for(int xIndex = 0; xIndex < X_WIDTH; xIndex++)
            {
                for(int zIndex = 0; zIndex < Z_WIDTH; zIndex++)
                {
                    byte red;
                    byte green;
                    byte blue;

                    SetRainbowColorMap
                    (
                        valueArray[xIndex, zIndex],
                        yMinimum,
                        yMaximum,
                        out red,
                        out green,
                        out blue
                    );

                    bitmapPixelMaker.SetPixel(xIndex, zIndex, red, green, blue, 255);
                }
            }

            WriteableBitmap bitmap = bitmapPixelMaker.GetBitmap(96, 96);

            bitmap.Save("Texture.png");
        }

        #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, pointCollection, point3D)

        /// <summary>
        /// 포인트 추가하기
        /// </summary>
        /// <param name="point3DCollection">3차원 포인트 컬렉션</param>
        /// <param name="pointCollection">포인트 컬렉션</param>
        /// <param name="point3D">3차원 포인트</param>
        /// <returns>포인트 인덱스</returns>
        private int AddPoint(Point3DCollection point3DCollection, PointCollection pointCollection, Point3D point3D)
        {
            if(this.point3DDictionary.ContainsKey(point3D))
            {
                return this.point3DDictionary[point3D];
            }

            point3DCollection.Add(point3D);

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

            pointCollection.Add
            (
                new Point
                (
                    (point3D.X - X_MINIMUM) * TEXTURE_X_SCALE,
                    (point3D.Z - Z_MINIMUM) * TEXTURE_Z_SCALE
                )
            );

            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, meshGeometry3D.TextureCoordinates, point3D1);
            int index2 = AddPoint(meshGeometry3D.Positions, meshGeometry3D.TextureCoordinates, point3D2);
            int index3 = AddPoint(meshGeometry3D.Positions, meshGeometry3D.TextureCoordinates, 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();

            for(double x = X_MINIMUM; x <= X_MAXIMUM - DELTA_X; x += DELTA_X)
            {
                for(double z = Z_MINIMUM; z <= Z_MAXIMUM - DELTA_Z; z += DELTA_X)
                {
                    Point3D p00 = new Point3D(x          , GetY(x          , z          ), z          );
                    Point3D p10 = new Point3D(x + DELTA_X, GetY(x + DELTA_X, z          ), z          );
                    Point3D p01 = new Point3D(x          , GetY(x          , z + DELTA_Z), z + DELTA_Z);
                    Point3D p11 = new Point3D(x + DELTA_X, GetY(x + DELTA_X, z + DELTA_Z), z + DELTA_Z);

                    AddTriangle(meshGeometry3D, p00, p01, p11);
                    AddTriangle(meshGeometry3D, p00, p11, p10);
                }
            }

            ImageBrush imageBrush = new ImageBrush();

            imageBrush.ImageSource = new BitmapImage(new Uri("Texture.png", UriKind.Relative));

            DiffuseMaterial diffuseMaterial = new DiffuseMaterial(imageBrush);

            GeometryModel3D geometryModel3D = new GeometryModel3D(meshGeometry3D, diffuseMaterial);

            geometryModel3D.BackMaterial = diffuseMaterial;

            model3DGroup.Children.Add(geometryModel3D);
        }

        #endregion
    }
}
728x90
반응형
Posted by 사용자 icodebroker
TAG , , ,

댓글을 달아 주세요