Список работ

C#, OpenGL, OpenTK: шейдер с картой-кубом в задаче воспроизведения глобуса

Содержание

Введение

Шейдеры вершин и фрагментов с картой-кубом, хранящей образ географической карты Земли, употребляются для воспроизведения глобуса – карты земли на сфере (рис. 1).

Карта земли на сфере

Рис. 1. Глобус

Для получения результата использованы C#, OpenGL библиотеки OpenTK.
Приводимый пример – это слегка модифицированный код примера DDS Cube Map, имеющийся в демонстрационном пакете OpenTK.
На основе образов образов файла earthCubemap.dds (DDS – Direct Draw surface file) создается текстура TMU0_Unitt, для наложения которой используются GLSL-шейдеры вершин и фрагментов, входящие в состав OpenTK (GLSL – Graphics Library Shader Language).
Используется консоль-проект, в пространство имен которого добавляются недостающие ссылки (Solution Explorre – References – правая кнопка мыши – Add Reference – Browse):

using System;
using OpenTK; // WindowState , Exit() and so on
using OpenTK.Graphics.OpenGL;

Описываемые далее файл образов и файлы шейдеров могут быть получены по следующей ссылке: скачать dds- и glsl-файлы
пространство имен TextureLoaders, поддержиивающее загрузку текстур, такое же, что в примере Шейдер с иллюзией выступов и впадин
Создание полигональной модели сферы (рис. 2) обеспечивается пространством имен Shapes.

Полигональная модель сферы

Рис. 2. Полигональная модель сферы

Файлы образов

Файл earthCubeMap.dds содержит 6 образов, в совокупности представляющих географическую карту Земли (рис. 2).

Карта-куб с образом карты Земли

Рис. 3. Образы файла earthCubeMap.dds – куски карты Земли

Так же на рис. 3 показаны свойства файла earthCubeMap.dds, одинаковые для всех образов.

Шейдеры вершин и фрагментов

Код шейдеров вершин и фрагментов загружается соответственно из файлов CubeMap_VS.glsl и CubeMap_FS.glsl.
Код шейдера вершин:

// Copyright (c) 2008 the OpenTK Team
// MUST be written to for FS
varying vec3 Normal;
void main()
{
  gl_Position = ftransform();
  Normal = /*gl_NormalMatrix * */ gl_Normal;
}

Код шейдера фрагментов:

// Copyright (c) 2008 the OpenTK Team
uniform samplerCube Earth;
varying vec3 Normal;
void main()
{
  gl_FragColor = textureCube(Earth, Normal.xyz);
}

Шейдер фрагментов получает от шейдера вершин вектор нормали и, оперируя данными dds-файла, вычисляет цвет текущего пикселя (gl_FragColor).

Вывод сферы с картой-кубом

В приводимой ниже программе изображение управляется движениями мыши (см. обработчики OnUpdateFrame и OnRenderFrame).
Создаваемая текстура с картой Земли основана на образах карты-куба:

Код приложения:

// Based on code from the OpenTK library
using System;
using System.Collections.Generic; // List
using System.IO;

using OpenTK;
using OpenTK.Graphics.OpenGL;

using Shapes;
using TextureLoaders;

namespace earthCubemap
{
    public class T13_GLSL_Earth: GameWindow
    {
        public T13_GLSL_Earth() : base(600, 600)
        {
        }
        // Шейдер
        int VertexShaderObject, FragmentShaderObject, ProgramObject;
        const string VertexShaderFilename = "G:/AM/CubeMap_VS.glsl";
        const string FragmentShaderFilename = "G:/AM/CubeMap_FS.glsl";
        // Текстура
        const TextureUnit TMU0_Unit = TextureUnit.Texture0;
        const int TMU0_UnitInteger = 0;
        const string TMU0_Filename = "G:/AM/earthCubemap.dds";
        uint TMU0_Handle;
        TextureTarget TMU0_Target;
        // DL
        DrawableShape sphere;
        // Камера
        Vector3 EyePos = new Vector3(0.0f, 0.0f, 6.0f);
        Vector3 Trackball = Vector3.Zero;

        protected override void OnLoad(EventArgs e)
        {
            this.VSync = VSyncMode.Off;
            // Проверки возможностей текущей версии OpenGL
            string extensions = GL.GetString(StringName.Extensions);
            if (!GL.GetString(StringName.Extensions).Contains("GL_ARB_shading_language"))
                throw new NotSupportedException(String.Format("Пример тербует OpenGL версии 2.0. Найдена {0}. Отказ.",
                    GL.GetString(StringName.Version).Substring(0, 3)));
            if (!extensions.Contains("GL_ARB_texture_compression") || !extensions.Contains("GL_EXT_texture_compression_s3tc"))
                throw new NotSupportedException("Пример требует поддержки сжатия текстур. Отказ.");
            GL.ClearColor(0.5f, 0.5f, 0.5f, 0f); // Серый цвет
            GL.Disable(EnableCap.Dither);
            GL.Enable(EnableCap.CullFace);
            GL.FrontFace(FrontFaceDirection.Ccw); // Лицевые грани обходятся против часовой стрелки
            GL.PolygonMode(MaterialFace.Front, PolygonMode.Fill);
            string LogInfo;
            // Загрузка и компиляция шейдера вершин
            using (StreamReader sr = new StreamReader(VertexShaderFilename))
            {
                VertexShaderObject = GL.CreateShader(ShaderType.VertexShader);
                GL.ShaderSource(VertexShaderObject, sr.ReadToEnd());
                GL.CompileShader(VertexShaderObject);
            }
            GL.GetShaderInfoLog(VertexShaderObject, out LogInfo);
            if (LogInfo.Length > 0 && !LogInfo.Contains("hardware"))
                Console.WriteLine("Ошибка компиляции шейдера вершин\nLog:\n" + LogInfo);
            else
                Console.WriteLine("Компиляция шейдера вершин завершена успешно");
            // Загрузка и компиляция шейдера фрагментов
            using (StreamReader sr = new StreamReader(FragmentShaderFilename))
            {
                FragmentShaderObject = GL.CreateShader(ShaderType.FragmentShader);
                GL.ShaderSource(FragmentShaderObject, sr.ReadToEnd());
                GL.CompileShader(FragmentShaderObject);
            }
            GL.GetShaderInfoLog(FragmentShaderObject, out LogInfo);
            if (LogInfo.Length > 0 && !LogInfo.Contains("hardware"))
                Console.WriteLine("Ошибка компиляции шейдера фрагментов\nLog:\n" + LogInfo);
            else
                Console.WriteLine("Компиляция шейдера фрагментов завершена успешно");
            // Связываем шейдеры с рабочей программой
            ProgramObject = GL.CreateProgram();
            GL.AttachShader(ProgramObject, VertexShaderObject);
            GL.AttachShader(ProgramObject, FragmentShaderObject);
            // Связываем все вместе
            GL.LinkProgram(ProgramObject);
            // Удаляем ранее созданные шейдеры
            GL.DeleteShader(VertexShaderObject);
            GL.DeleteShader(FragmentShaderObject);
            int[] temp = new int[1];
            GL.GetProgram(ProgramObject, GetProgramParameterName.LinkStatus, out temp[0]);
            Console.WriteLine("Связывание программ (" + ProgramObject + ") " + ((temp[0] == 1) ? "выполнено." : "НЕ ВЫПОЛНЕНО"));
            if (temp[0] != 1) // В случае неудачи при связывании
            {
                GL.GetProgramInfoLog(ProgramObject, out LogInfo);
                Console.WriteLine("Program Log:\n" + LogInfo);
            }
            GL.GetProgram(ProgramObject, GetProgramParameterName.ActiveAttributes, out temp[0]);
            Console.WriteLine("Зарегестрировано " + temp[0] + " атрибута. (Должно быть 4: Pos, UV, Normal, Tangent)");
            Console.WriteLine("Положение атрибута AttributeTangent: " + GL.GetAttribLocation(ProgramObject, "AttributeTangent"));
            Console.WriteLine("Создание шейдера завершено. GL-ошибка: " + GL.GetError());
            ImageDDS.LoadFromDisk(TMU0_Filename, false, false, out TMU0_Handle, out TMU0_Target);
            Console.WriteLine("Загружен " + TMU0_Filename + " с обработчиком " + TMU0_Handle + " как " + TMU0_Target);
            Console.WriteLine("Загрузка текстур завершена. GL-ошибка: " + GL.GetError());
            Console.WriteLine("");
            sphere = new SlicedSphere(1.5f, Vector3d.Zero, SlicedSphere.eSubdivisions.Four, new SlicedSphere.eDir[] { SlicedSphere.eDir.All }, true);
        }
        protected override void OnUnload(EventArgs e)
        {
            sphere.Dispose();
            GL.DeleteProgram(ProgramObject);
            GL.DeleteTextures(1, ref TMU0_Handle);
            base.OnUnload(e);
        }
        // Обработчик изменения размеров окна графического вывода
        protected override void OnResize(EventArgs e)
        {
            GL.Viewport(0, 0, Width, Height);
            GL.MatrixMode(MatrixMode.Projection);
            Matrix4 p = Matrix4.CreatePerspectiveFieldOfView(MathHelper.PiOver4, Width / (float)Height, 0.1f, 10.0f);
            GL.LoadMatrix(ref p);
            GL.MatrixMode(MatrixMode.Modelview);
            GL.LoadIdentity();
            base.OnResize(e);
        }
        // Обработчик обновления окна графического вывода
        protected override void OnUpdateFrame(FrameEventArgs e)
        {
            base.OnUpdateFrame(e);
            var keyboard = OpenTK.Input.Keyboard.GetState();
            var mouse = OpenTK.Input.Mouse.GetState();
            if (keyboard[OpenTK.Input.Key.Escape]) this.Exit();
            if (keyboard[OpenTK.Input.Key.Space]) Console.WriteLine("GL: " + GL.GetError());
            // Запоминаем координаты и прокрутку мыши
            Trackball.X = mouse.X;
            Trackball.Y = mouse.Y;
            Trackball.Z = mouse.Scroll.Y * 0.5f;
        }
        // Обработчик воспроизведения
        protected override void OnRenderFrame(FrameEventArgs e)
     {
         this.Title = "FPS: " + (1 / e.Time).ToString("0.");
         GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
         GL.UseProgram(ProgramObject);
         GL.ActiveTexture(TMU0_Unit);
         GL.BindTexture(TMU0_Target, TMU0_Handle);
         GL.Uniform1(GL.GetUniformLocation(ProgramObject, "Earth"), TMU0_UnitInteger);
         GL.PushMatrix();
         Matrix4 temp = Matrix4.LookAt(EyePos, Vector3.Zero, Vector3.UnitY);
         GL.MultMatrix(ref temp);
         GL.Rotate(Trackball.X, Vector3.UnitY);
         GL.Rotate(Trackball.Y, Vector3.UnitX);
         GL.Color3(1f, 1f, 1f);
         sphere.Draw();
         GL.PopMatrix();
         this.SwapBuffers();
     }
        // Entry point
        [STAThread]
        public static void Main()
        {
            using (T13_GLSL_Earth earth = new T13_GLSL_Earth())
            {
                earth.Title = "Earth cube mapping";
                earth.Run(30.0, 0.0);
            }
        }
    }
}
namespace TextureLoaders
{
    // Нужна подходящая версия OpenGL с поддержкой сжатия текстур (GL 1.5) и кубических карт (GL 1.3)
    // Необходимо, чтобы размеры текстуры делились на 4, поскольку DXTn использует блоки 4x4
    // Кубическая карта должна быть задана для всех шести сторон куба
    static class ImageDDS
    {
        private const byte HeaderSizeInBytes = 128; // Рабзмер заголовка в байтах
        private const uint BitMask = 0x00000007; // биты 00 00 01 11
        private static NotImplementedException Unfinished = new NotImplementedException("ERROR: Only 2 Dimensional DXT1/3/5 compressed images for now. 1D/3D Textures may not be compressed according to spec.");
        private static bool _IsCompressed;
        private static int _Width, _Height, _Depth, _MipMapCount;
        private static int _BytesForMainSurface;
        private static byte _BytesPerBlock;
        private static PixelInternalFormat _PixelInternalFormat;
        [Flags] // Описание поверхности
        private enum eDDSD : uint
        {
            CAPS = 0x00000001, // is always present
            HEIGHT = 0x00000002, // is always present
            WIDTH = 0x00000004, // is always present
            PITCH = 0x00000008, // is set if the image is uncompressed
            PIXELFORMAT = 0x00001000, // is always present
            MIPMAPCOUNT = 0x00020000, // is set if the image contains MipMaps
            LINEARSIZE = 0x00080000, // is set if the image is compressed
            DEPTH = 0x00800000 // is set for 3D Volume Textures
        }
        [Flags] // Формат пикселя
        private enum eDDPF : uint
        {
            NONE = 0x00000000, // Not part of DX, added for convenience
            ALPHAPIXELS = 0x00000001,
            FOURCC = 0x00000004,
            RGB = 0x00000040,
            RGBA = 0x00000041
        }
        // Перечень взят из nVidia OpenGL SDK
        [Flags] // Типы текстур
        private enum eFOURCC : uint
        {
            UNKNOWN = 0,
            DXT1 = 0x31545844,
            DXT2 = 0x32545844,
            DXT3 = 0x33545844,
            DXT4 = 0x34545844,
            DXT5 = 0x35545844,
        }
        [Flags] // dwCaps1
        private enum eDDSCAPS : uint
        {
            NONE = 0x00000000, // not part of DX, added for convenience
            COMPLEX = 0x00000008, // should be set for any DDS file with more than one main surface
            TEXTURE = 0x00001000, // should always be set
            MIPMAP = 0x00400000 // only for files with MipMaps
        }
        [Flags] // dwCaps2
        private enum eDDSCAPS2 : uint
        {
            NONE = 0x00000000, // not part of DX, added for convenience
            CUBEMAP = 0x00000200,
            CUBEMAP_POSITIVEX = 0x00000400,
            CUBEMAP_NEGATIVEX = 0x00000800,
            CUBEMAP_POSITIVEY = 0x00001000,
            CUBEMAP_NEGATIVEY = 0x00002000,
            CUBEMAP_POSITIVEZ = 0x00004000,
            CUBEMAP_NEGATIVEZ = 0x00008000,
            CUBEMAP_ALL_FACES = 0x0000FC00,
            VOLUME = 0x00200000 // for 3D Textures
        }
        private static string idString; // 4 байта, должно быть "DDS"
        private static UInt32 dwSize; // Size of structure is 124 bytes, 128 including all sub-structs and the header
        private static UInt32 dwFlags; // Flags to indicate valid fields
        private static UInt32 dwHeight; // Height of the main image in pixels
        private static UInt32 dwWidth; // Width of the main image in pixels
        private static UInt32 dwPitchOrLinearSize; // For compressed formats, this is the total number of bytes for the main image
        private static UInt32 dwDepth; // For volume textures, this is the depth of the volume
        private static UInt32 dwMipMapCount; // Total number of levels in the mipmap chain of the main image
        // Pixelformat sub-struct, 32 bytes
        private static UInt32 pfSize; // Size of Pixelformat structure. This member must be set to 32
        private static UInt32 pfFlags; // Flags to indicate valid fields
        private static UInt32 pfFourCC; // This is the four-character code for compressed formats
        // Capabilities sub-struct, 16 bytes
        private static UInt32 dwCaps1; // Always includes DDSCAPS_TEXTURE with more than one main surface DDSCAPS_COMPLEX should also be set
        // For cubic environment maps DDSCAPS2_CUBEMAP should be included
        // as well as one or more faces of the map (DDSCAPS2_CUBEMAP_POSITIVEX,
        // DDSCAPS2_CUBEMAP_NEGATIVEX, DDSCAPS2_CUBEMAP_POSITIVEY,
        // DDSCAPS2_CUBEMAP_NEGATIVEY, DDSCAPS2_CUBEMAP_POSITIVEZ, DDSCAPS2_CUBEMAP_NEGATIVEZ)
        // For volume textures, DDSCAPS2_VOLUME should be included
        private static UInt32 dwCaps2;
        // This function will generate, bind and fill a Texture Object with a DXT1/3/5 compressed Texture in .dds Format.
        // MipMaps below 4x4 Pixel Size are discarded, because DXTn's smallest unit is a 4x4 block of Pixel data.
        // It will set correct MipMap parameters, Filtering, Wrapping and EnvMode for the Texture.
        // The only call inside this function affecting OpenGL State is GL.BindTexture();
        // The name of the file you wish to load, including path and file extension
        // 0 if invalid, otherwise a Texture Object usable with GL.BindTexture()
        // 0 if invalid, will output what was loaded (typically Texture1D/2D/3D or Cubemap)
        public static void LoadFromDisk(string filename, bool FlipImages, bool Verbose, out uint texturehandle, out TextureTarget dimension)
        {
            // Начальные установки
            dimension = (TextureTarget)0;
            ErrorCode GLError = ErrorCode.NoError;
            _IsCompressed = false;
            _Width = 0;
            _Height = 0;
            _Depth = 0;
            _MipMapCount = 0;
            _BytesForMainSurface = 0;
            _BytesPerBlock = 0;
            _PixelInternalFormat = PixelInternalFormat.Rgba8;
            byte[] _RawDataFromFile;
            try // Исключение возникнет при ошибках чтения файла
            {
                _RawDataFromFile = File.ReadAllBytes(@filename);
                ConvertDX9Header(ref _RawDataFromFile); // The first 128 Bytes of the file is non-image data
                // Поверка всех констант и флагов
                if (idString != "DDS " || // magic key
                     dwSize != 124 || // constant size of struct, never reused
                     pfSize != 32 || // constant size of struct, never reused
                     !CheckFlag(dwFlags, (uint)eDDSD.CAPS) ||        // must know it's caps
                     !CheckFlag(dwFlags, (uint)eDDSD.PIXELFORMAT) || // must know it's format
                     !CheckFlag(dwCaps1, (uint)eDDSCAPS.TEXTURE)     // must be a Texture
                 )
                    throw new ArgumentException("ERROR: File has invalid signature or missing Flags.");
                if (CheckFlag(dwFlags, (uint)eDDSD.WIDTH))
                    _Width = (int)dwWidth;
                else
                    throw new ArgumentException("ERROR: Flag for Width not set.");
                if (CheckFlag(dwFlags, (uint)eDDSD.HEIGHT))
                    _Height = (int)dwHeight;
                else
                    throw new ArgumentException("ERROR: Flag for Height not set.");
                if (CheckFlag(dwFlags, (uint)eDDSD.DEPTH) && CheckFlag(dwCaps2, (uint)eDDSCAPS2.VOLUME))
                {
                    dimension = TextureTarget.Texture3D; // image is 3D Volume
                    _Depth = (int)dwDepth;
                    throw Unfinished;
                }
                else
                {// Куб или образ 2D
                    if (CheckFlag(dwCaps2, (uint)eDDSCAPS2.CUBEMAP))
                    {
                        dimension = TextureTarget.TextureCubeMap;
                        _Depth = 6;
                    }
                    else
                    {
                        dimension = TextureTarget.Texture2D;
                        _Depth = 1;
                    }
                }
                // Устанавливаем флаг при наличии mip-карт
                if (CheckFlag(dwCaps1, (uint)eDDSCAPS.MIPMAP) && CheckFlag(dwFlags, (uint)eDDSD.MIPMAPCOUNT))
                    _MipMapCount = (int)dwMipMapCount; // Образ содержит mip-карты
                else
                    _MipMapCount = 1; // Только один главный образ
                // Should never happen
                if (CheckFlag(dwFlags, (uint)eDDSD.PITCH) && CheckFlag(dwFlags, (uint)eDDSD.LINEARSIZE))
                    throw new ArgumentException("ОШИБКА: Одновременно указаны флаги Наклонный и Линейный. Образ не может быть одновременно несжатым и DTXn-сжатым");
                // This flag is set if format is uncompressed RGB RGBA etc.
                if (CheckFlag(dwFlags, (uint)eDDSD.PITCH))
                {
                    _IsCompressed = false;
                    throw Unfinished;
                }
                // This flag is set if format is compressed DXTn.
                if (CheckFlag(dwFlags, (uint)eDDSD.LINEARSIZE))
                {
                    _BytesForMainSurface = (int)dwPitchOrLinearSize;
                    _IsCompressed = true;
                }
                if (CheckFlag(pfFlags, (uint)eDDPF.FOURCC))
                    switch ((eFOURCC)pfFourCC)
                    {
                        case eFOURCC.DXT1:
                            _PixelInternalFormat = (PixelInternalFormat)ExtTextureCompressionS3tc.CompressedRgbS3tcDxt1Ext;
                            _BytesPerBlock = 8;
                            _IsCompressed = true;
                            break;
                        case eFOURCC.DXT3:
                            _PixelInternalFormat = (PixelInternalFormat)ExtTextureCompressionS3tc.CompressedRgbaS3tcDxt3Ext;
                            _BytesPerBlock = 16;
                            _IsCompressed = true;
                            break;
                        case eFOURCC.DXT5:
                            _PixelInternalFormat = (PixelInternalFormat)ExtTextureCompressionS3tc.CompressedRgbaS3tcDxt5Ext;
                            _BytesPerBlock = 16;
                            _IsCompressed = true;
                            break;
                        default:
                            throw Unfinished; // Handle uncompressed formats
                    }
                else
                    throw Unfinished;
                if (Verbose) Console.WriteLine("\n" + GetDescriptionFromMemory(filename, dimension));
                GL.GenTextures(1, out texturehandle);
                GL.BindTexture(dimension, texturehandle);
                int Cursor = HeaderSizeInBytes;
                // Для кубической карты получаем все mip-карты. Только одна итерация для Texture2D
                for (int Slices = 0; Slices < _Depth; Slices++)
                {
                    int trueMipMapCount = _MipMapCount - 1;
                    int Width = _Width;
                    int Height = _Height;
                    for (int Level = 0; Level < _MipMapCount; Level++) // Начинаем с базового образа
                    {
                        int BlocksPerRow = (Width + 3) >> 2;
                        int BlocksPerColumn = (Height + 3) >> 2;
                        int SurfaceBlockCount = BlocksPerRow * BlocksPerColumn; // DXTn stores Texels in 4x4 blocks, a Color block is 8 Bytes, an Alpha block is 8 Bytes for DXT3/5
                        int SurfaceSizeInBytes = SurfaceBlockCount * _BytesPerBlock;
                        // This check must evaluate to false for 2D and Cube maps, or it's impossible to determine MipMap sizes.
                        if (Verbose && Level == 0 && _IsCompressed && _BytesForMainSurface != SurfaceSizeInBytes)
                            Console.WriteLine("Warning: Calculated byte-count of main image differs from what was read from file.");
                        // Skip mipmaps smaller than a 4x4 Pixels block, which is the smallest DXTn unit
                        if (Width > 2 && Height > 2)
                        { // Замечание: при работе с образами, размеры которых не являются степенью числа 2, моогут быть проблемы
                            byte[] RawDataOfSurface = new byte[SurfaceSizeInBytes];
                            if (!FlipImages)
                            { // Без изменений образа
                                Array.Copy(_RawDataFromFile, Cursor, RawDataOfSurface, 0, SurfaceSizeInBytes);
                            }
                            else
                            { // Поворот образа на 180 градусов
                                for (int sourceColumn = 0; sourceColumn < BlocksPerColumn; sourceColumn++)
                                {
                                    int targetColumn = BlocksPerColumn - sourceColumn - 1;
                                    for (int row = 0; row < BlocksPerRow; row++)
                                    {
                                        int target = (targetColumn * BlocksPerRow + row) * _BytesPerBlock;
                                        int source = (sourceColumn * BlocksPerRow + row) * _BytesPerBlock + Cursor;
                                        switch (_PixelInternalFormat)
                                        {
                                            case (PixelInternalFormat)ExtTextureCompressionS3tc.CompressedRgbS3tcDxt1Ext:
                                                // Color only
                                                RawDataOfSurface[target + 0] = _RawDataFromFile[source + 0];
                                                RawDataOfSurface[target + 1] = _RawDataFromFile[source + 1];
                                                RawDataOfSurface[target + 2] = _RawDataFromFile[source + 2];
                                                RawDataOfSurface[target + 3] = _RawDataFromFile[source + 3];
                                                RawDataOfSurface[target + 4] = _RawDataFromFile[source + 7];
                                                RawDataOfSurface[target + 5] = _RawDataFromFile[source + 6];
                                                RawDataOfSurface[target + 6] = _RawDataFromFile[source + 5];
                                                RawDataOfSurface[target + 7] = _RawDataFromFile[source + 4];
                                                break;
                                            case (PixelInternalFormat)ExtTextureCompressionS3tc.CompressedRgbaS3tcDxt3Ext:
                                                // Alpha
                                                RawDataOfSurface[target + 0] = _RawDataFromFile[source + 6];
                                                RawDataOfSurface[target + 1] = _RawDataFromFile[source + 7];
                                                RawDataOfSurface[target + 2] = _RawDataFromFile[source + 4];
                                                RawDataOfSurface[target + 3] = _RawDataFromFile[source + 5];
                                                RawDataOfSurface[target + 4] = _RawDataFromFile[source + 2];
                                                RawDataOfSurface[target + 5] = _RawDataFromFile[source + 3];
                                                RawDataOfSurface[target + 6] = _RawDataFromFile[source + 0];
                                                RawDataOfSurface[target + 7] = _RawDataFromFile[source + 1];
                                                // Color
                                                RawDataOfSurface[target + 8] = _RawDataFromFile[source + 8];
                                                RawDataOfSurface[target + 9] = _RawDataFromFile[source + 9];
                                                RawDataOfSurface[target + 10] = _RawDataFromFile[source + 10];
                                                RawDataOfSurface[target + 11] = _RawDataFromFile[source + 11];
                                                RawDataOfSurface[target + 12] = _RawDataFromFile[source + 15];
                                                RawDataOfSurface[target + 13] = _RawDataFromFile[source + 14];
                                                RawDataOfSurface[target + 14] = _RawDataFromFile[source + 13];
                                                RawDataOfSurface[target + 15] = _RawDataFromFile[source + 12];
                                                break;
                                            case (PixelInternalFormat)ExtTextureCompressionS3tc.CompressedRgbaS3tcDxt5Ext:
                                                // Alpha, the first 2 bytes remain
                                                RawDataOfSurface[target + 0] = _RawDataFromFile[source + 0];
                                                RawDataOfSurface[target + 1] = _RawDataFromFile[source + 1];
                                                // extract 3 bits each and flip them
                                                GetBytesFromUInt24(ref RawDataOfSurface, (uint)target + 5, FlipUInt24(GetUInt24(ref _RawDataFromFile, (uint)source + 2)));
                                                GetBytesFromUInt24(ref RawDataOfSurface, (uint)target + 2, FlipUInt24(GetUInt24(ref _RawDataFromFile, (uint)source + 5)));
                                                // Color
                                                RawDataOfSurface[target + 8] = _RawDataFromFile[source + 8];
                                                RawDataOfSurface[target + 9] = _RawDataFromFile[source + 9];
                                                RawDataOfSurface[target + 10] = _RawDataFromFile[source + 10];
                                                RawDataOfSurface[target + 11] = _RawDataFromFile[source + 11];
                                                RawDataOfSurface[target + 12] = _RawDataFromFile[source + 15];
                                                RawDataOfSurface[target + 13] = _RawDataFromFile[source + 14];
                                                RawDataOfSurface[target + 14] = _RawDataFromFile[source + 13];
                                                RawDataOfSurface[target + 15] = _RawDataFromFile[source + 12];
                                                break;
                                            default:
                                                throw new ArgumentException("ERROR: Should have never arrived here! Bad _PixelInternalFormat! Should have been dealt with much earlier.");
                                        }
                                    }
                                }
                            }
                            switch (dimension)
                            {
                                case TextureTarget.Texture2D:
                                    GL.CompressedTexImage2D(TextureTarget.Texture2D, Level, _PixelInternalFormat,
                                        Width, Height, 0, SurfaceSizeInBytes, RawDataOfSurface);
                                    break;
                                case TextureTarget.TextureCubeMap:
                                    GL.CompressedTexImage2D(TextureTarget.TextureCubeMapPositiveX + Slices, Level, _PixelInternalFormat,
                                        Width, Height, 0, SurfaceSizeInBytes, RawDataOfSurface);
                                    break;
                                default:
                                    throw new ArgumentException("Ошибка: Используйтеп DXT для 2D-образов. Не могу работать с " + dimension);
                            }
                            GL.Finish();
                            int width, height, internalformat, compressed;
                            switch (dimension)
                            {
                                case TextureTarget.Texture1D:
                                case TextureTarget.Texture2D:
                                case TextureTarget.Texture3D:
                                    GL.GetTexLevelParameter(dimension, Level, GetTextureParameter.TextureWidth, out width);
                                    GL.GetTexLevelParameter(dimension, Level, GetTextureParameter.TextureHeight, out height);
                                    GL.GetTexLevelParameter(dimension, Level, GetTextureParameter.TextureInternalFormat, out internalformat);
                                    GL.GetTexLevelParameter(dimension, Level, GetTextureParameter.TextureCompressed, out compressed);
                                    break;
                                case TextureTarget.TextureCubeMap:
                                    GL.GetTexLevelParameter(TextureTarget.TextureCubeMapPositiveX + Slices, Level, GetTextureParameter.TextureWidth, out width);
                                    GL.GetTexLevelParameter(TextureTarget.TextureCubeMapPositiveX + Slices, Level, GetTextureParameter.TextureHeight, out height);
                                    GL.GetTexLevelParameter(TextureTarget.TextureCubeMapPositiveX + Slices, Level, GetTextureParameter.TextureInternalFormat, out internalformat);
                                    GL.GetTexLevelParameter(TextureTarget.TextureCubeMapPositiveX + Slices, Level, GetTextureParameter.TextureCompressed, out compressed);
                                    break;
                                default:
                                    throw Unfinished;
                            }
                            GLError = GL.GetError();
                            if (Verbose)
                                Console.WriteLine("GL: " + GLError.ToString() + " Level: " + Level + " DXTn: " + ((compressed == 1) ? "Yes" : "No") + " Frmt:" + (ExtTextureCompressionS3tc)internalformat + " " + width + "*" + height);
                            if (GLError != ErrorCode.NoError || compressed == 0 || width == 0 || height == 0 || internalformat == 0)
                            {
                                GL.DeleteTextures(1, ref texturehandle);
                                throw new ArgumentException("ERROR: Something went wrong after GL.CompressedTexImage(); Last GL Error: " + GLError.ToString());
                            }
                        }
                        else
                            if (trueMipMapCount > Level) trueMipMapCount = Level - 1; // The current Level is invalid
                        Width /= 2;
                        if (Width < 1) Width = 1;
                        Height /= 2;
                        if (Height < 1) Height = 1;
                        Cursor += SurfaceSizeInBytes;
                    }
                    GL.TexParameter(dimension, (TextureParameterName)All.TextureBaseLevel, 0);
                    GL.TexParameter(dimension, (TextureParameterName)All.TextureMaxLevel, trueMipMapCount);
                    int TexMaxLevel;
                    GL.GetTexParameter(dimension, GetTextureParameter.TextureMaxLevel, out TexMaxLevel);
                    if (Verbose)
                        Console.WriteLine("Verification: GL: " + GL.GetError().ToString() + " TextureMaxLevel: " + TexMaxLevel + ((TexMaxLevel == trueMipMapCount) ? " (Correct.)" : " (Wrong!)"));
                }
                GL.TexParameter(dimension, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
                GL.TexParameter(dimension, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
                GL.TexParameter(dimension, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
                GL.TexParameter(dimension, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);
                GL.TexEnv(TextureEnvTarget.TextureEnv, TextureEnvParameter.TextureEnvMode, (int)TextureEnvMode.Modulate);
                GLError = GL.GetError();
                if (GLError != ErrorCode.NoError)
                    throw new ArgumentException("Ошибка при установки параметров тестуры. GL-ошибка: " + GLError);
                return; // Удача
            }
            catch (Exception e)
            {
                dimension = (TextureTarget)0;
                throw new ArgumentException("ERROR: Exception caught when attempting to load file " + filename + ".\n" + e + "\n" + GetDescriptionFromFile(filename));
            }
            finally
            {
                _RawDataFromFile = null;
            }
        }
        private static void ConvertDX9Header(ref byte[] input)
        {
            UInt32 offset = 0;
            idString = GetString(ref input, offset);
            offset += 4;
            dwSize = GetUInt32(ref input, offset);
            offset += 4;
            dwFlags = GetUInt32(ref input, offset);
            offset += 4;
            dwHeight = GetUInt32(ref input, offset);
            offset += 4;
            dwWidth = GetUInt32(ref input, offset);
            offset += 4;
            dwPitchOrLinearSize = GetUInt32(ref input, offset);
            offset += 4;
            dwDepth = GetUInt32(ref input, offset);
            offset += 4;
            dwMipMapCount = GetUInt32(ref input, offset);
            offset += 4;
            offset += 4 * 11;
            pfSize = GetUInt32(ref input, offset);
            offset += 4;
            pfFlags = GetUInt32(ref input, offset);
            offset += 4;
            pfFourCC = GetUInt32(ref input, offset);
            offset += 4;
            offset += 20;
            dwCaps1 = GetUInt32(ref input, offset);
            offset += 4;
            dwCaps2 = GetUInt32(ref input, offset);
            offset += 4;
            offset += 4 * 3;
        }
        // Returns true if the flag is set, false otherwise
        private static bool CheckFlag(uint variable, uint flag)
        {
            return (variable & flag) > 0 ? true : false;
        }
        private static string GetString(ref byte[] input, uint offset)
        {
            return "" + (char)input[offset + 0] + (char)input[offset + 1] + (char)input[offset + 2] + (char)input[offset + 3];
        }
        private static uint GetUInt32(ref byte[] input, uint offset)
        {
            return (uint)(((input[offset + 3] * 256 + input[offset + 2]) * 256 + input[offset + 1]) * 256 + input[offset + 0]);
        }
        private static uint GetUInt24(ref byte[] input, uint offset)
        {
            return (uint)((input[offset + 2] * 256 + input[offset + 1]) * 256 + input[offset + 0]);
        }
        private static void GetBytesFromUInt24(ref byte[] input, uint offset, uint splitme)
        {
            input[offset + 0] = (byte)(splitme & 0x000000ff);
            input[offset + 1] = (byte)((splitme & 0x0000ff00) >> 8);
            input[offset + 2] = (byte)((splitme & 0x00ff0000) >> 16);
            return;
        }
        // DXT5 Alpha block flipping, inspired by code from Evan Hart (nVidia SDK)
        private static uint FlipUInt24(uint inputUInt24)
        {
            byte[][] ThreeBits = new byte[2][];
            for (int i = 0; i < 2; i++) ThreeBits[i] = new byte[4];
            // Extract 3 bits each into the array
            ThreeBits[0][0] = (byte)(inputUInt24 & BitMask);
            inputUInt24 >>= 3;
            ThreeBits[0][1] = (byte)(inputUInt24 & BitMask);
            inputUInt24 >>= 3;
            ThreeBits[0][2] = (byte)(inputUInt24 & BitMask);
            inputUInt24 >>= 3;
            ThreeBits[0][3] = (byte)(inputUInt24 & BitMask);
            inputUInt24 >>= 3;
            ThreeBits[1][0] = (byte)(inputUInt24 & BitMask);
            inputUInt24 >>= 3;
            ThreeBits[1][1] = (byte)(inputUInt24 & BitMask);
            inputUInt24 >>= 3;
            ThreeBits[1][2] = (byte)(inputUInt24 & BitMask);
            inputUInt24 >>= 3;
            ThreeBits[1][3] = (byte)(inputUInt24 & BitMask);
            // Stuff 8x 3bits into 3 bytes
            uint Result = 0;
            Result = Result | (uint)(ThreeBits[1][0] << 0);
            Result = Result | (uint)(ThreeBits[1][1] << 3);
            Result = Result | (uint)(ThreeBits[1][2] << 6);
            Result = Result | (uint)(ThreeBits[1][3] << 9);
            Result = Result | (uint)(ThreeBits[0][0] << 12);
            Result = Result | (uint)(ThreeBits[0][1] << 15);
            Result = Result | (uint)(ThreeBits[0][2] << 18);
            Result = Result | (uint)(ThreeBits[0][3] << 21);
            return Result;
        }
        private static string GetDescriptionFromFile(string filename)
        {
            return "\n--> Header of " + filename +
                 "\nID: " + idString +
                 "\nSize: " + dwSize +
                 "\nFlags: " + dwFlags + " (" + (eDDSD)dwFlags + ")" +
                 "\nHeight: " + dwHeight +
                 "\nWidth: " + dwWidth +
                 "\nPitch: " + dwPitchOrLinearSize +
                 "\nDepth: " + dwDepth +
                 "\nMipMaps: " + dwMipMapCount +
                 "\n\n---PixelFormat---" + filename +
                 "\nSize: " + pfSize +
                 "\nFlags: " + pfFlags + " (" + (eDDPF)pfFlags + ")" +
                 "\nFourCC: " + pfFourCC + " (" + (eFOURCC)pfFourCC + ")" +
                 "\n\n---Capabilities---" + filename +
                 "\nCaps1: " + dwCaps1 + " (" + (eDDSCAPS)dwCaps1 + ")" +
                 "\nCaps2: " + dwCaps2 + " (" + (eDDSCAPS2)dwCaps2 + ")";
        }
        private static string GetDescriptionFromMemory(string filename, TextureTarget Dimension)
        {
            return "\nFile: " + filename +
                 "\nDimension: " + Dimension +
                 "\nSize: " + _Width + " * " + _Height + " * " + _Depth +
                 "\nCompressed: " + _IsCompressed +
                 "\nBytes for Main Image: " + _BytesForMainSurface +
                 "\nMipMaps: " + _MipMapCount;
        }
    }
}
namespace Shapes
{
    // Abstract base class for procedurally generated geometry
    // All classes derived from it must produce Counter-Clockwise (CCW) primitives.
    // Derived classes must create a single VBO and IBO, without primitive restarts for strips.
    // Uses an double-precision all-possible-attributes VertexT2dN3dV3d Array internally.
    // Cannot directly use VBO, but has Get-methods to retrieve VBO-friendly data.
    // Can use a Display List to prevent repeated immediate mode draws.
    // VBO - Vertex Buffer Object
    public abstract class DrawableShape : IDisposable
    {
        protected PrimitiveType PrimitiveMode;
        protected VertexT2dN3dV3d[] VertexArray;
        protected uint[] IndexArray;
        public int GetTriangleCount
        {
            get
            {
                switch (PrimitiveMode)
                {
                    case PrimitiveType.Triangles:
                        if (IndexArray != null)
                        {
                            return IndexArray.Length / 3;
                        }
                        else
                        {
                            return VertexArray.Length / 3;
                        }
                    // break;
                    default: throw new NotImplementedException("Unknown primitive type.");
                }
            }
        }
        private bool UseDisplayList;
        private int DisplayListHandle = 0;
        public DrawableShape(bool useDisplayList)
        {
            UseDisplayList = useDisplayList;
            PrimitiveMode = PrimitiveType.Triangles;
            VertexArray = null;
            IndexArray = null;
        }
        public void GetArraysforVBO(out PrimitiveType primitives, out VertexT2dN3dV3d[] vertices, out uint[] indices)
        {
            primitives = PrimitiveMode;
            vertices = new VertexT2dN3dV3d[VertexArray.Length];
            for (uint i = 0; i < VertexArray.Length; i++)
            {
                vertices[i].TexCoord = VertexArray[i].TexCoord;
                vertices[i].Normal = VertexArray[i].Normal;
                vertices[i].Position = VertexArray[i].Position;
            }
            indices = IndexArray;
        }
        public void GetArraysforVBO(out PrimitiveType primitives, out VertexT2fN3fV3f[] vertices, out uint[] indices)
        {
            primitives = PrimitiveMode;
            vertices = new VertexT2fN3fV3f[VertexArray.Length];
            for (uint i = 0; i < VertexArray.Length; i++)
            {
                vertices[i].TexCoord = (Vector2)VertexArray[i].TexCoord;
                vertices[i].Normal = (Vector3)VertexArray[i].Normal;
                vertices[i].Position = (Vector3)VertexArray[i].Position;
            }
            indices = IndexArray;
        }
        public void GetArraysforVBO(out PrimitiveType primitives, out VertexT2hN3hV3h[] vertices, out uint[] indices)
        {
            primitives = PrimitiveMode;
            vertices = new VertexT2hN3hV3h[VertexArray.Length];
            for (uint i = 0; i < VertexArray.Length; i++)
            {
                vertices[i].TexCoord = (Vector2h)VertexArray[i].TexCoord;
                vertices[i].Normal = (Vector3h)VertexArray[i].Normal;
                vertices[i].Position = (Vector3h)VertexArray[i].Position;
            }
            indices = IndexArray;
        }
        private void DrawImmediateMode()
        {
            GL.Begin(PrimitiveMode);
            {
                if (IndexArray == null)
                    foreach (VertexT2dN3dV3d v in VertexArray)
                    {
                        GL.TexCoord2(v.TexCoord.X, v.TexCoord.Y);
                        GL.Normal3(v.Normal.X, v.Normal.Y, v.Normal.Z);
                        GL.Vertex3(v.Position.X, v.Position.Y, v.Position.Z);
                    }
                else
                {
                    for (uint i = 0; i < IndexArray.Length; i++)
                    {
                        uint index = IndexArray[i];
                        GL.TexCoord2(VertexArray[index].TexCoord.X, VertexArray[index].TexCoord.Y);
                        GL.Normal3(VertexArray[index].Normal.X, VertexArray[index].Normal.Y, VertexArray[index].Normal.Z);
                        GL.Vertex3(VertexArray[index].Position.X, VertexArray[index].Position.Y, VertexArray[index].Position.Z);
                    }
                }
            }
            GL.End();
        }
        // Does not touch any state/matrices. Does call Begin/End and Vertex&Co.
        // Creates and compiles a display list if not present yet. Requires an OpenGL context.
        public void Draw()
        {
            if (!UseDisplayList)
                DrawImmediateMode();
            else
                if (DisplayListHandle == 0)
                {
                    if (VertexArray == null)
                        throw new Exception("Cannot draw null Vertex Array.");
                    DisplayListHandle = GL.GenLists(1);
                    GL.NewList(DisplayListHandle, ListMode.CompileAndExecute);
                    DrawImmediateMode();
                    GL.EndList();
                }
                else
                    GL.CallList(DisplayListHandle);
        }
        // Removes reference to VertexArray and IndexArray
        // Deletes the Display List, so it requires an OpenGL context
        // The instance is effectively destroyed
        public void Dispose()
        {
            if (VertexArray != null) VertexArray = null;
            if (IndexArray != null) IndexArray = null;
            if (DisplayListHandle != 0)
            {
                GL.DeleteLists(DisplayListHandle, 1);
                DisplayListHandle = 0;
            }
        }
    }
    public sealed class SlicedSphere : DrawableShape
    {
        public enum eSubdivisions
        {
            Zero = 0,
            One = 1,
            Two = 2,
            Three = 3,
            Four = 4,
            Five = 5,
            Six = 6,
            Seven = 7,
            Eight = 8,
        }
        public enum eDir
        {
            All,
            FrontTopRight,
            FrontBottomRight,
            FrontBottomLeft,
            FrontTopLeft,
            BackTopRight,
            BackBottomRight,
            BackBottomLeft,
            BackTopLeft,
        }
        public SlicedSphere(double radius, Vector3d offset, eSubdivisions subdivs, eDir[] sides, bool useDL) : base(useDL)
        {
            double Diameter = radius;
            PrimitiveMode = OpenTK.Graphics.OpenGL.PrimitiveType.Triangles;
            if (sides[0] == eDir.All)
            {
                sides = new eDir[] {
                    eDir.FrontTopRight,
                    eDir.FrontBottomRight,
                    eDir.FrontBottomLeft,
                    eDir.FrontTopLeft,
                    eDir.BackTopRight,
                    eDir.BackBottomRight,
                    eDir.BackBottomLeft,
                    eDir.BackTopLeft,};
            }
            VertexArray = new VertexT2dN3dV3d[sides.Length * 3];
            IndexArray = new uint[sides.Length * 3];
            uint counter = 0;
            foreach (eDir s in sides)
            {
                GetDefaultVertices(s, Diameter, out VertexArray[counter + 0], out VertexArray[counter + 1], out VertexArray[counter + 2]);
                IndexArray[counter + 0] = counter + 0;
                IndexArray[counter + 1] = counter + 1;
                IndexArray[counter + 2] = counter + 2;
                counter += 3;
            }
            if (subdivs != eSubdivisions.Zero)
            {
                for (int s = 0; s < (int)subdivs; s++)
                {
                    List<Chunk> AllChunks = new List<Chunk>();
                    for (uint i = 0; i < IndexArray.Length; i += 3)
                    {
                        Chunk chu;
                        Subdivide(Diameter,
                                 ref VertexArray[IndexArray[i + 0]],
                                 ref VertexArray[IndexArray[i + 1]],
                                 ref VertexArray[IndexArray[i + 2]],
                                 out chu);
                        AllChunks.Add(chu);
                    }
                    Chunk.GetArray(ref AllChunks, out VertexArray, out IndexArray);
                    AllChunks.Clear();
                }
            }
            for (int i = 0; i < VertexArray.Length; i++)
            {
                Vector3d.Add(ref VertexArray[i].Position, ref offset, out VertexArray[i].Position);
            }
        }
        private void GetDefaultVertices(eDir s, double scale, out VertexT2dN3dV3d first, out VertexT2dN3dV3d second, out VertexT2dN3dV3d third)
        {
            VertexT2dN3dV3d t1 = new VertexT2dN3dV3d(), t2 = new VertexT2dN3dV3d(), t3 = new VertexT2dN3dV3d();
            switch (s)
            {
                case eDir.FrontTopRight:
                    t1 = new VertexT2dN3dV3d(new Vector2d(0.5, 1.0), Vector3d.UnitY, Vector3d.UnitY * scale);
                    t2 = new VertexT2dN3dV3d(new Vector2d(0.0, 0.0), Vector3d.UnitZ, Vector3d.UnitZ * scale);
                    t3 = new VertexT2dN3dV3d(new Vector2d(0.5, 0.0), Vector3d.UnitX, Vector3d.UnitX * scale);
                    break;
                case eDir.FrontBottomRight:
                    t1 = new VertexT2dN3dV3d(new Vector2d(0.5, 0.0), Vector3d.UnitX, Vector3d.UnitX * scale);
                    t2 = new VertexT2dN3dV3d(new Vector2d(0.0, 0.0), Vector3d.UnitZ, Vector3d.UnitZ * scale);
                    t3 = new VertexT2dN3dV3d(new Vector2d(0.5, 1.0), -Vector3d.UnitY, -Vector3d.UnitY * scale);
                    break;
                case eDir.FrontBottomLeft:
                    t1 = new VertexT2dN3dV3d(new Vector2d(0.5, 0.0), Vector3d.UnitX, Vector3d.UnitX * scale);
                    t2 = new VertexT2dN3dV3d(new Vector2d(0.5, 1.0), -Vector3d.UnitY, -Vector3d.UnitY * scale);
                    t3 = new VertexT2dN3dV3d(new Vector2d(1.0, 0.0), -Vector3d.UnitZ, -Vector3d.UnitZ * scale);
                    break;
                case eDir.FrontTopLeft:
                    t1 = new VertexT2dN3dV3d(new Vector2d(1.0, 0.0), -Vector3d.UnitZ, -Vector3d.UnitZ * scale);
                    t2 = new VertexT2dN3dV3d(new Vector2d(0.5, 1.0), Vector3d.UnitY, Vector3d.UnitY * scale);
                    t3 = new VertexT2dN3dV3d(new Vector2d(0.5, 0.0), Vector3d.UnitX, Vector3d.UnitX * scale);
                    break;
                case eDir.BackTopRight:
                    t1 = new VertexT2dN3dV3d(new Vector2d(0.5, 1.0), Vector3d.UnitY, Vector3d.UnitY * scale);
                    t2 = new VertexT2dN3dV3d(new Vector2d(0.0, 1.0), -Vector3d.UnitX, -Vector3d.UnitX * scale);
                    t3 = new VertexT2dN3dV3d(new Vector2d(0.0, 0.0), Vector3d.UnitZ, Vector3d.UnitZ * scale);
                    break;
                case eDir.BackBottomRight:
                    t1 = new VertexT2dN3dV3d(new Vector2d(0.5, 1.0), -Vector3d.UnitY, -Vector3d.UnitY * scale);
                    t2 = new VertexT2dN3dV3d(new Vector2d(0.0, 0.0), Vector3d.UnitZ, Vector3d.UnitZ * scale);
                    t3 = new VertexT2dN3dV3d(new Vector2d(0.0, 1.0), -Vector3d.UnitX, -Vector3d.UnitX * scale);
                    break;
                case eDir.BackBottomLeft:
                    t1 = new VertexT2dN3dV3d(new Vector2d(0.5, 1.0), -Vector3d.UnitY, -Vector3d.UnitY * scale);
                    t2 = new VertexT2dN3dV3d(new Vector2d(1.0, 1.0), -Vector3d.UnitX, -Vector3d.UnitX * scale);
                    t3 = new VertexT2dN3dV3d(new Vector2d(1.0, 0.0), -Vector3d.UnitZ, -Vector3d.UnitZ * scale);
                    break;
                case eDir.BackTopLeft:
                    t1 = new VertexT2dN3dV3d(new Vector2d(0.5, 1.0), Vector3d.UnitY, Vector3d.UnitY * scale);
                    t2 = new VertexT2dN3dV3d(new Vector2d(1.0, 0.0), -Vector3d.UnitZ, -Vector3d.UnitZ * scale);
                    t3 = new VertexT2dN3dV3d(new Vector2d(1.0, 1.0), -Vector3d.UnitX, -Vector3d.UnitX * scale);
                    break;
            }
            first = t1;
            second = t2;
            third = t3;
        }
        private void Subdivide(double Scale, ref VertexT2dN3dV3d first, ref VertexT2dN3dV3d second, ref VertexT2dN3dV3d third, out Chunk c)
        {
            c = new Chunk(6, 12);
            c.Vertices[0] = first;
            Vector3d.Lerp(ref first.Position, ref second.Position, 0.5, out c.Vertices[1].Normal);
            c.Vertices[1].Normal.Normalize();
            c.Vertices[1].Position = c.Vertices[1].Normal * Scale;
            Vector2d.Lerp(ref first.TexCoord, ref second.TexCoord, 0.5, out c.Vertices[1].TexCoord);
            Vector3d.Lerp(ref third.Position, ref first.Position, 0.5, out c.Vertices[2].Normal);
            c.Vertices[2].Normal.Normalize();
            c.Vertices[2].Position = c.Vertices[2].Normal * Scale;
            Vector2d.Lerp(ref third.TexCoord, ref first.TexCoord, 0.5, out c.Vertices[2].TexCoord);
            c.Vertices[3] = second;
            Vector3d.Lerp(ref second.Position, ref third.Position, 0.5, out c.Vertices[4].Normal);
            c.Vertices[4].Normal.Normalize();
            c.Vertices[4].Position = c.Vertices[4].Normal * Scale;
            Vector2d.Lerp(ref second.TexCoord, ref third.TexCoord, 0.5, out c.Vertices[4].TexCoord);
            c.Vertices[5] = third;
            c.Indices[0] = 0;
            c.Indices[1] = 1;
            c.Indices[2] = 2;
            c.Indices[3] = 2;
            c.Indices[4] = 1;
            c.Indices[5] = 4;
            c.Indices[6] = 1;
            c.Indices[7] = 3;
            c.Indices[8] = 4;
            c.Indices[9] = 2;
            c.Indices[10] = 4;
            c.Indices[11] = 5;
        }
    }
    public class Chunk
    {
        public VertexT2dN3dV3d[] Vertices;
        public uint[] Indices;
        public uint VertexCount
        {
            get
            {
                return (uint)Vertices.Length;
            }
        }
        public uint IndexCount
        {
            get
            {
                return (uint)Indices.Length;
            }
        }
        public Chunk(uint vertexcount, uint indexcount)
        {
            Vertices = new VertexT2dN3dV3d[vertexcount];
            Indices = new uint[indexcount];
        }
        public Chunk(ref VertexT2dN3dV3d[] vbo, ref uint[] ibo)
        {
            Vertices = new VertexT2dN3dV3d[vbo.Length];
            for (int i = 0; i < Vertices.Length; i++) Vertices[i] = vbo[i];
            Indices = new uint[ibo.Length];
            for (int i = 0; i < Indices.Length; i++) Indices[i] = ibo[i];
        }
        public static void GetArray(ref List<Chunk> c, out VertexT2dN3dV3d[] vbo, out uint[] ibo)
        {
            uint VertexCounter = 0;
            uint IndexCounter = 0;
            foreach (Chunk ch in c)
            {
                VertexCounter += ch.VertexCount;
                IndexCounter += ch.IndexCount;
            }
            vbo = new VertexT2dN3dV3d[VertexCounter]; // Vertex Buffer Object
            ibo = new uint[IndexCounter];
            VertexCounter = 0;
            IndexCounter = 0;
            foreach (Chunk ch in c)
            {
                for (int i = 0; i < ch.Vertices.Length; i++) vbo[VertexCounter + i] = ch.Vertices[i];
                for (int i = 0; i < ch.Indices.Length; i++) ibo[IndexCounter + i] = ch.Indices[i] + VertexCounter;
                VertexCounter += (uint)ch.VertexCount;
                IndexCounter += (uint)ch.IndexCount;
            }
        }
    }
    public struct VertexT2dN3dV3d
    {
        public Vector2d TexCoord;
        public Vector3d Normal;
        public Vector3d Position;
        public VertexT2dN3dV3d(Vector2d texcoord, Vector3d normal, Vector3d position)
        {
            TexCoord = texcoord;
            Normal = normal;
            Position = position;
        }
    }
    public struct VertexT2fN3fV3f
    {
        public Vector2 TexCoord;
        public Vector3 Normal;
        public Vector3 Position;
    }
    public struct VertexT2hN3hV3h
    {
        public Vector2h TexCoord;
        public Vector3h Normal;
        public Vector3h Position;
    }
}

Список работ

Рейтинг@Mail.ru