Визуализация простой геометрии в WPF

Что такое геометрия модели


Для работы с 3D моделями мы используем специальные конвейеры обработки — OpenGL и DirectX. Когда конвейеры строят картину, они используют информацию:

  • о модели — её материале, геометрии и текстурах,
  • о сцене — освещении и настройке камеры.

Любая модель начинается с геометрии. Геометрия модели — это набор точек в трехмерном пространстве и набор треугольников из этих точек. Треугольник компланарен — он лежит в плоскости, в отличие от фигур с большим числом точек, которые в общем случае не лежат в плоскости.

Если задать у треугольника направление обхода границы, он станет ориентированным треугольником. У ориентированного треугольника в трехмерном пространстве можно выделить внутреннюю и внешнюю стороны. По направлению обхода границы мы также определяем единственный по направлению вектор нормали для каждого треугольника. Так, в геометрию модели, помимо точек и треугольников, входит набор нормалей к каждому из треугольников. При отображении модели точки модели соответствуют точкам, треугольники модели соответствуют граням.

В примере мы сосредоточимся на геометрии модели, остальное будем использовать по мере необходимости. В качестве примера модели вполне подойдет Дельфин ниже:

image

Благодаря технологии WPF мы создаем интерактивные интерфейсы приложений и работаем с 3D-графикой. В примере мы используем стандартные возможности архитектуры WPF: привяжем данные и на их основе разделим модель данных и представление данных (MVVM).

Основной элемент для отображения 3D-содержимого в библиотеке WPF Viewport3D. Например, свойство Camera устанавливает камеру, и мы видим сцену. Второе необходимое свойство Viewport3D Children, коллекция элементов абстрактного типа Visual3D. Конкретная реализация этого класса — класс ModelVisual3D: чтобы его использовать, нужно указать свойство Content абстрактного типа Model3D.

Основные классы для установки свойства Content:

  • GeometryModel3D — отображает одну модель,
  • Light — модель источника света,
  • Model3DGroup — помогает создавать модели с разными материалами.

Необходимые свойства будем устанавливать по привязке.

Модель данных MVVM


В широком смысле любое приложение решает определенную задачу. Модель должна полностью отражать данные в решаемой приложением задаче. Мы упростим пример и исключим нормали — они будут определяться по умолчанию. Нормали важны для отображения текстур или заливки при расчете освещенности.

Подберем основные интерфейсы, которые определяют сущности модели данных MVVM и их связи:

interface IModel3DSet {
        string Description { get; set; }
        ICollection<IModel3D> Models { get; }
    }

interface IModel3D {
        string Description { get; set; }
        ICollection<IPoint3D> Points { get; }
        ICollection<ITriangle3D> Triangles { get; }
    }

interface IPoint3D {
        double X { get; set; }
        double Y { get; set; }
        double Z { get; set; }
        string Coordinates { get; }

        IVector3D DistanceTo(IPoint3D endPoint);
    }

interface ITriangle3D {
        IModel3D Model3D { get; }
        IPoint3D Point1 { get; set; }
        IPoint3D Point2 { get; set; }
        IPoint3D Point3 { get; set; }
    }

interface IVector3D {
        double X { get; set; }
        double Y { get; set; }
        double Z { get; set; }
        double Norm { get; }

        IVector3D Add(IVector3D vector);
        IVector3D Subtract(IVector3D vector);
        IVector3D Multiply(double factor);

        IVector3D CrossProduct(IVector3D vector);
        double DotProduct(IVector3D vector);
    }

Конкретная реализация прямолинейна. Чтобы предупреждать об изменении свойств, используем привычный INotifyPropertyChanged.

ViewModel


В качестве базового класса для ViewModel мы используем:

public abstract class BaseVm<TModel> : Notifier {
        TModel _model;
        public TModel Model {
            get { return _model; }
            set {
                _model = value;
                NotifyWithCallerPropName();
            }
        }
    }

public abstract class BaseVm<TModel, TParentVM> : BaseVm<TModel> {
        public BaseVm(TModel model = default(TModel), TParentVM parentVM = default(TParentVM)) {
            Model = model;
            Parent = parentVM;
        }
        public TParentVM Parent { get; }
    }

Такая структура удобна тем, что позволяет двигаться по иерархии ViewModel в привязках. Классы Vector3D, Triangle3D, Point3D просты, поэтому создавать для них ViewModel не обязательно. Значит нам нужны только два класса ViewModel — Model3DSetVm и Model3DVm.

Представления


Чтобы построить представления, используем подстановку WPF с помощью атрибута DataType="{x:Type local:Type}" при объявлении DataTemplate в словарях ресурсов. В остальном реализация стандартная. Приложение для демонстрации выглядит так:



Что еще нужно знать


  1. В WPF свойство TriangleIndices не обязательное. Если оно не задано, по умолчанию будут созданы треугольники по каждой тройке точек. По этой причине даже при пустом наборе треугольников отображаются грани.

  2. Чтобы создать привязку для коллекции моделей, можно использовать привязку из нашего примера, но подставьте экземпляр Model3DGroup вместо GeometryModel3D. Использовать для этого привязку к свойству Children Viewport3D не получится.

  3. Если мы изменим геометрию или ось вращения, модель перестроится, изображение будет дергаться — анимация начнется заново. Чтобы процесс не прерывался, сохраняйте промежуточные значения модели и применяйте к ним анимацию.

Проект с примером можно найти здесь...

Буду рад, если статья вам в чем-то поможет…
  • +13
  • 5,5k
  • 8
Tinkoff.ru 108,97
Самый большой онлайн-банк в мире
Поделиться публикацией

Вакансии компании Tinkoff.ru

Комментарии 8
  • 0
    Возможно, глупый вопрос, но почему ICollection, а не IList? Какие вообще преимущества у коллекции перед списком? Никак не могу найти сравнительную таблицу всех этих списочно-коллекционных сущностей в дотнете.
    • 0
      А таблицу и не нужно искать. Просто посмотрите имплементации одного интерфейса и другого.
      public interface ICollection<T> : IEnumerable<T>, IEnumerable
      {
          int Count { get; }
          bool IsReadOnly { get; }
       
          void Add(T item);
          void Clear();
          bool Contains(T item);
          void CopyTo(T[] array, int arrayIndex);
          bool Remove(T item);
      }
      
      
      public interface IList<T> : ICollection<T>, IEnumerable<T>, IEnumerable
      {
          T this[int index] { get; set; }
       
          int IndexOf(T item);
          void Insert(int index, T item);
          void RemoveAt(int index);
      }
      


      IList наследуется от ICollection, и поддерживает все тоже самое + индексация объектов в коллекции. Т.е. если вам важен порядок объектов в коллекции используйте IList, если порядок не важен и вы нигде не обращаетесь по его индексу ( вставляете/удаляете по индексу), то используйте ICollection. Смысл в том, чтобы не использовать, то что не нужно.
      • 0
        Спасибо, примерно так и думал. Но почему бы и не использовать IList на всякий случай, мало ли как жизнь сложится. Минусов ведь никаких?
        • 0
          затем чтобы не закладывать лишних ограничений без необходимости. поменять на IList вы сможете всегда, а вот завязавшись на его методы, обратно путь будет сложнее.
          • 0
            Поэтому, наверно, и таблиц нет.
            • 0
              С точки зрения клиента библиотеки, лучше ICollection, т.к. есть маневр для выбора типа коллекции.

              Но, с точки зрения разработчика библиотеки, раз пообещав ICollection, уже нельзя захотеть принимать IList, чтобы не сломать совместимость со всем уже написанным сторонним кодом. Поэтому, если есть предположение, что для будущей функциональности пригодится произвольный доступ, может быть удобнее заранее потребовать более сильные условия, чтобы клиенты изначально рассчитывали на эти условия.
        • 0
          есть хорошая библиотека для работы с 3D WPF — HelixToolkit.Wpf
          • 0
            Спасибо за описание. Много чего интересного для себя почерпнул.

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое
            Интересные публикации