БЕЛОРУССКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ИНФОРМАТИКИ И РАДИОЭЛЕКТРОНИКИ кафедра ЭВМ Пояснительная записка курсовой работе по теме: «Кроссплатформенная программа по моделирования опытов по геометрической оптике» Выполнил: Проверил: ст.гр. 750501 Пынькин Д.А. Кисель Р.А. Минск 2010 1 Оглавление 1 ВВЕДЕНИЕ .................................................................................................... 3 1.1 Постановка задачи, возможности, плюсы и минусы программы. ......................................... 4 1.2 Сравнительный анализ программного обеспечения по моделированию физических процессов. 5 1.3 Инструментарий .............................................................................................................................. 6 1.4 Структура входных и выходных данных ................................................................................... 7 2 ОПИСАНИЕ КЛАССОВ ................................................................................ 9 2.1 Класс OptixItem ............................................................................................................................. 10 2.2 Класс Border ................................................................................................................................... 10 2.3 Класс PLight ................................................................................................................................... 11 2.4 Класс DLight ................................................................................................................................... 12 2.5 Класс OptixScene............................................................................................................................ 13 3 ОСНОВНЫЕ АЛГОРИТМЫ ........................................................................ 17 3.1 - Прорисовка теней от непрозрачных объектов. ..................................................................... 17 3.2 - Трассировка луча........................................................................................................................ 20 4 КОПИИ ЭКРАНА ПРОГРАММЫ: ............................................................... 22 2 1 Введение Оптика - раздел физики, рассматривающий явления, связанные с распространением электромагнитных волн преимущественно видимого и близких к нему диапазонов (инфракрасное и ультрафиолетовое излучение). Оптика описывает свойства света и объясняет связанные с ним явления. Методы оптики используются во многих прикладных дисциплин, включая электротехнику, физику, медицину (в частности, офтальмологию). В этих, а также в междисциплинарных сферах широко применяются достижения прикладной оптики. Геометрическая оптика — раздел оптики, изучающий законы распространения света в прозрачных средах и принципы построения изображений при прохождении света в оптических системах без учёта его волновых свойств. История геометрической оптики берет начало еще с 300 в. до н.э., когда древнегреческий математик Евклид в своем трактате «Оптика» доказал прямолинейность распространения света. Позже явления геометрической оптики исследовали такие ученые, как Клавдий Птолемей (исследовал преломление света на границе воздух—вода и воздух—стекло), Ибн ал-Хайсам (Альхазен) (изучал законы преломления и отражения света. Одним из первых высказал мысль о том, что источником световых лучей является не глаз, а светящиеся предметы.), Иоганн Кеплер (в трактате «Дополнения к Виттелию» («Оптическая астрономия», 1604) изложил основы геометрической оптики, сформулировал закон об обратно пропорциональной зависимости освещённости и квадрата расстояния от источника.), Виллеброрд Снелл (в 1621 году открыл закон преломления света (закон Снеллиуса)). Возможность достаточно широкого применения геометрической оптики в различных областях, таких как образование, офтальмология и др., 3 подтолкнуло на написание программного обеспечения по моделированию оптических явлений. 1.1 Постановка задачи, возможности, плюсы и минусы программы. Для чего же предназначена эта программа? Во-первых, с помощью это программы можно наглядно демонстрировать основные явления геометрической оптики. Такие как прямолинейность распространения света, закон отражения, закон преломления, и все возможные «побочные» этих явления (закон полного внутреннего отражения, законы преломления в различных типах линз). Во-вторых, с помощью это программы можно рассчитывать некоторые характеристики оптических систем на основе демонстрируемых опытов, такие как углы преломления и отражения, характеристики линз. В-третьих, простота графического интерфейса позволяет использовать эту программу для обучения школьников геометрической оптике. Это позволит школьникам лучше понять некоторые темы геометрической оптики благодаря наглядности демонстрируемых опытов, а преподавателям освободить свое время на долгое изнурительное объяснение тем оптики. К основным возможностям можно отнести: 1) Два типа источников света: точечный и направленный. 2) Непрозрачные объекты любой сложности. 3) Прозрачные объекты с любым коэффициентом преломления. 4) Зеркала. 5) Выпуклые, вогнутые линзы. 6) Измерение параметров с помощью встроенной линейки и транспортира. 7) Полная свобода в расположении объектов в опыте. Множество настроек. 8) Возможность сохранения и загрузки опыта. 4 К сожалению, ввиду нехватки времени не было реализовано несколько очень интересных моментов, таких как моделировать волновые опыты оптики (дисперсия, дифракция, интерференция). 1.2 Сравнительный анализ программного обеспечения по моделированию физических процессов. В процессе изучения программых продуктов, аналогичных данному (программа моделирования физических процессов) было обнаружено, что все эти программы можно условно разделить на несколько типов. Это программы, предназначенные для обучения физике, а также программы, предназначенные для исследования физических явлений. Первая группа программ используется в основном в учреждениях образования для обучения школьников или студентов младших курсов некоторым разделам физики. Структура этих программ в основном схожа. Это фиксированный набор опытов, которые предназначены для изучения какого-нибудь физического явления, и включают в себя средства измерения физических параметров для проведения лабораторных работ, дополненные теоретическими сведениями из соответствующих им разделов физики. Примером подобного типа программ является обучающая программа «Открытая физика» компании Физикон. В эту группу можно также отнести обучающие физике игры. Например «Физикус», «Физикус 2: возвращение», а также культовая в определенных кругах «Ксюха спешит на помощь: Физика». Ко второй группе можно отнести программы, целью которых является изучение физических явлений, что может быть использовано в научных, военных, промышленных и многих других областях. В основном такие программы включают в себя мощный математический инструмент моделирования, в большинстве своем основанный на решении дифференциальных уравнений, описывающий физические явления. Это все дает очень точную и подробную картину физического явления. Эти 5 программы являются достаточно универсальными в использовании, и область их применения не ограничивается рассматриваемым опытом. С их помощью можно моделировать явления из разных разделов физики, основываясь на одном универсальном инструменте. Примером такой программного средства является мощный программный пакет FEMlab компании COMSOL. Также в интернетах снискали популярность огромное количество java апплетов, демонстрирующие отдельные физические явления. Все же недостатком этого на мой взгляд является ограниченность демонстраций одним физическим явлением (один апплет – один опыт). Пример – сайт американского физика-программиста-java-апплетов (: http://www.falstad.com/mathphysics.html. Отличный сайт содержит большое количество физических опытов по волнам. К сожалению, существует не очень большое количество программ, которые позволяют моделировать опыты по оптике. 1.3 Инструментарий Для того, чтобы создать нужное программное обеспечение, необходимо оптимально подобрать инструменты создания программы. Так как требуется создать кроссплатформенное приложение, то на мой взгляд оптимальной средой является Qt. Именно этот набор библиотек с удобным дизайнером форм и редактором кода является отличным выбором для тех, кто пишет кроссплатформенные приложения. Qt позволяет запускать написанное с его помощью ПО в большинстве современных операционных систем путём простой компиляции программы для каждой ОС без изменения исходного кода. Включает в себя все основные классы, которые могут потребоваться при разработке прикладного программного обеспечения, начиная от элементов графического 6 интерфейса и заканчивая классами для работы с сетью, базами данных и XML. Qt является полностью объектно-ориентированным, легко расширяемым и поддерживающим технику компонентного программирования. Qt предоставляет большое количество готовых библиотек. Здесь есть и библиотеки для разработки графического интерфейса, для рисования графических примитивов, для работы с тетями и многое-многое другое. Но основными классами, которые были использованы в работе являются классы QGraphicsView, QGraphicsScene и QGraphicsItem. Они работают по принципу Model-View-Controller. Вид позволяет рисовать сцену независимо от объектов, которые в ней располагаются. Это предоставляет удобную возможность управлять отдельными объектами сцены. Вторым удобным инструментом разработки ПО является система контроля версий. В качестве системы контроля версий я вырал Subversion. Почему именно SVN (сокр. Subversion)? Во-первых, для нее существует удобный клиент для Windows Tortoise SVN, который позволяет осуществлять операции с репозиторием прямо из браузера файлов. Во-воторых, она совместима с code.google.com, который является удобнейшим репозиторием open source проектов. В-третьих, он отлично встраивается в Qt creator, что позволяет управлять ревизиями прямо из редактора кода Qt. В целом, это не единственная система контроля версий, которая удовлетворяет этим требованиям. Отличная система – это git, которую пишет сам Л. Торвальдс. 1.4 Структура входных и выходных данных В качестве формата хранения файлов опытов программы был выбран стандарт XML. Этот формат был выбран во-первых потому, что с ним достаточно просто работать. Qt предоставляет удобный класс QtXml для чтения и записи файлов в формате XML. Во-вторых, файлы XML очень просто редактировать 7 прямо из текстового редактора. Пользователь может без помощи программы открыть файл xml в любом текстовом редакторе и изменить интересующие его параметры сцены (расположение объектов, информацию об отдельном объекте и тд). 8 2 Описание классов Перечислим все классы, которые были реализованы в проекте: 1) Border – класс непрозрачного объекта. Эта преграда, которая создает тени и через которую свет не проходит и не отражается. 2) TransparentBorder – прозрачный объект. Обладая коэффициентом преломления, этот объект способен пропускать свет, преломляя его. 3) Mirror – зеркало. Свет через этот объект не проходит, а отражается. 4) PixmapItem – объект изображения. Можно отображать линейку или транспортир или другое изображение. 5) OptixItem – обобщенный класс. Наследуется предыдущими классами. Предназначен для работы с настройками объекта, такими как положение, коэффициент масштабирования, угол наклона. 6) PLight – класс точечного источника света. 7) DLigth – класс источника направленного света. 8) ShadowMap – класс для рисования теней. Объект этого класса рисуется в сцене поверх других объектов и из него вычитаются источники света. Тем самым создается эффект освещенности сцены. 9) ObjDrag – класс предназначен только для интерфейса, а точнее для возможности перетаскивания объектов в сцену легким движением манипулятора типа мышь. 10) MainWindow – класс главного окна приложения. Наследует графический интерфейс Form.ui, созданный в Qt designer. Дает доступ к объектам, расположенным на форме. В нем объявляются сигналы. 11) OptixScene – самый главный класс программы. Описывает логику работы. Хранит все объекты сцены. А теперь о самых интересных классах по порядку. 9 2.1 Класс OptixItem class OptixItem { protected: qreal scX; //коэффициент масштабир. По Х qreal scY; //коэффициент масштабир. По У qreal angle; //угол наклона QColor color; //цвет объекта public: OptixItem(QColor color); void setColor(QColor color); QColor getColor(); qreal getScX(); qreal getScY(); void setScX(qreal sx); void setScY(qreal sy); qreal getAngle(); void setAngle(qreal angle); }; Как видно, класс содержит 4 параметра и методы доступа к ним. Всё очень просто и в пояснении не нуждается. 2.2 Класс Border class Border : { private: qreal mH; public: Border( public QGraphicsPolygonItem, public OptixItem //Высота объекта qreal h = 5, QPolygonF *poly = NULL, QColor color = QColor(183, 65, 14)); qreal getH(); void setH(qreal h); void changeColor(); void contextMenuEvent( QGraphicsSceneContextMenuEvent *event); }; Класс наследует класс Qt объект-полигон и вышеописанный класс объекта нашей сцены. Параметр высота необходим для просчета теней. Чем выше объект, тем длиннее будет падающая от него тень. Метод 10 contextMenuEvent() вызывается при срабатывании события выпадающего меню (нажатие по объекту правой клавишей) 2.3 Класс PLight class PLight : public QGraphicsItem, public OptixItem { private: qreal mH; qreal mSize; //Высота источника //Размер. Дальность освещения ShadowMap *shadMap; //карта теней QRadialGradient *gradient; //градиент public: PLight( ShadowMap *shadMap, qreal h = 10, qreal size = 800, QColor color=QColor(255,255,255,130)); void drawLight(); qreal getH(); qreal getSize(); void setH(qreal h); void setSize(qreal size); void changeColor(); QRectF boundingRect() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); void contextMenuEvent( QGraphicsSceneContextMenuEvent *event); }; Класс наследуется от QGraphicsItem – класс объекта сцены Qt и OptixItem – класс объекта нашей сцены. Карта теней предназначена для более реалистичной прорисовки теней. Представьте, что изначально нет источников света и сцена полностью темная. Затем в нее добавляют источник, который освещает некоторые места сцены, то есть из карты теней вычитается всё, что освещено. Методы boundingRect()и paint()по соглашению Qt необходимо переопределить из класса QGraphicsItem. BoundingRect() возвращает 11 ограничивающий объект прямоугольник. Он необходим для взаимодействия полбзователя с объектом и используется в некоторых методах сцены. В методе Paint() происходит непосредственно прорисовка объекта. 2.4 Класс DLight class DLight : public QGraphicsItem, public OptixItem { private: qreal mD; //Ширина сточника qreal mLength; //Длина int mNumLines; //кол-во лучей int mLineSize; //размер луча ShadowMap *shadMap; public: DLight(ShadowMap *shadMap, qreal d = 30, qreal length = 2000, int numLines = 10, int lineSize = 4, QColor color = QColor(255, 255, 255, 100)); void drawPolygonLight(QPolygonF poly); void drawLightLine(QLineF line); qreal getD(); void setD(qreal d); qreal getLength(); void setLength(qreal length); int getNumLines(); void setNumLines(qreal nlines); int getLineSize(); void setLineSize(qreal lsize); QRectF boundingRect() const; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget); void contextMenuEvent(QGraphicsSceneContextMenuEvent *event); }; Класс похож на предыдущий за исключением нескольких параметров направленного источника. 12 2.5 Класс OptixScene class OptixScene : public QGraphicsScene { Q_OBJECT public: OptixScene(QObject *prnt = 0); //состояния сцены enum mode { ordinary, rotate, scale } curMode; enum saveMode { unnamed, named } curSaveMode; public: static const int cSceneW = 1500; static const int cSceneH = 1500; public slots: //слот вызывается каждый раз, когда сцена изменяется void changeDraw(const QList<QRectF> &); //слоты void selectItem(); void srlRotateChange(int); void srlScaleChange(int); void srlScaleXChange(int); void srlScaleYChange(int); //выбор объекта //вращение //масштабирование //масштабирование по X //масштабирование по Y void deleteItems(); //удаление //действия void actionRotate(); void actionScale(); void actionToFront(); void actionToBack(); void actionColor(); void actionSettings(); void actionNew(); void actionOpen(); void actionSave(); void actionSaveAs(); //кнопка вращения //кнопка масштабирования //на передний план //на задний план //изменить цвет //настройки //новый //открыть //сохранить //сохранить как public: void setContextMenu(QMenu *menu); void setToolBar(QToolBar *toolBar); QMenu *ContextMenu(); //меню //тулбар //прорисовка света void drawLigths(); void drawDLights(DLight *light); void traceRay(DLight *light, QLineF line); void drawPShadows(); void rotateItem(QGraphicsItem *item, int value); 13 void scaleItem(QGraphicsItem *item, qreal sx, qreal sy); //добавить новый предмет void addPLight(QPointF pos); void addDLight(QPointF pos); void addBorder(QPointF pos); void addBorderTriangle(QPointF pos); void addBorderGexagon(QPointF pos); void addMirror(QPointF pos); void addTPlate(QPointF pos); void addTrianglePrism(QPointF pos); void addConvexLens(QPointF pos); void addConvexLens2(QPointF pos); void addConcaveLens(QPointF pos); void addConcaveLens2(QPointF pos); void addRuler(QPointF pos); void addProtractor(QPointF pos); //получение объекта нужного типа QList<Border *> getBorders(); QList<TransparentBorder *> getTBorders(); QList<Mirror *> getMirrors(); QList<PLight *> getPLights(); QList<DLight *> getDLights(); //сохранение и загрузка void saveFile(QString fileName); bool openFile(QString fileName); //сохранение в XML формат void traverseNode(QDomNode &node); void loadElement(QString type, QDomNode &node); void loadInfo( QDomNode domNode, qreal &angle, qreal &sx, qreal &sy, QColor &color); void loadPolygon( QDomNode domNode, QPolygonF &poly); QDomElement makeInfoItem( QDomDocument &doc, QGraphicsItem *item); QDomElement addElement( QDomDocument &doc, QGraphicsItem *item); QDomElement makePolygonInfo(QDomDocument &doc, const QPolygonF &poly); protected: //события мыши void mousePressEvent( QGraphicsSceneMouseEvent *mouseEvent); void mouseMoveEvent( QGraphicsSceneMouseEvent *mouseEvent); //события перетаскивания 14 void dragEnterEvent(QGraphicsSceneDragDropEvent *event); void dropEvent(QGraphicsSceneDragDropEvent *event); void drawForeground( QPainter * painter, const QRectF & rect); private: //стек глубины трассировки луча QStack<TransparentBorder *> traceStack; QGraphicsRectItem *dropRect; ShadowMap shadMap; qreal densityAir; //плотность воздуха QMenu *contextMenu; QToolBar *toolBar; QGraphicsItem *curSelectItem; QString curFile; }; Сцена может находится в 3х состояниях – это обычное, когда с объектами можно взаимодействовать, вращение – когда мы вращаем выделенный предмет, но не можем взаимодействовать с другими объектами, и масштабирование, когда изменяем размер объекта. Вращение и масштабирование предметов происходит в методах: void void void void При srlRotateChange(int); srlScaleChange(int); srlScaleXChange(int); srlScaleYChange(int); взаимодействием с //вращение //масштабирование //масштабирование по X //масштабирование по Y объектами интерфейса вызываются соответствующие действия: //действия void actionRotate(); void actionScale(); void actionToFront(); void actionToBack(); void actionColor(); void actionSettings(); void actionNew(); void actionOpen(); void actionSave(); void actionSaveAs(); //кнопка вращения //кнопка масштабирования //на передний план //на задний план //изменить цвет //настройки //новый //открыть //сохранить //сохранить как Основные методы прорисовки света выделены в методы: //прорисовка света 15 void void void void drawLigths(); drawDLights(DLight *light); traceRay(DLight *light, QLineF line); drawPShadows(); Сохранение и загрузка сцены осуществляется в методах: //сохранение и загрузка void saveFile(QString fileName); bool openFile(QString fileName); //сохранение в XML формат void traverseNode(QDomNode &node); void loadElement(QString type, QDomNode &node); void loadInfo( QDomNode domNode, qreal &angle, qreal &sx, qreal &sy, QColor &color); void loadPolygon( QDomNode domNode, QPolygonF &poly); QDomElement makeInfoItem( QDomDocument &doc, QGraphicsItem *item); QDomElement addElement( QDomDocument &doc, QGraphicsItem *item); QDomElement makePolygonInfo(QDomDocument &doc, const QPolygonF &poly); 16 3 Основные алгоритмы Здесь рассмотрим некоторые самые важные и самые интересные алгоритмы. 3.1 - Прорисовка теней от непрозрачных объектов. Этот алгоритм вызывается каждый раз, когда изменяется цена, ведь когда изменяется положение любого предмета, то тени от него соответственно тоже должны измениться. Запишем алгоритм по шагам: 1) Цикл для каждого точечного источника света. 1.1) Цикл для каждого непрозрачного объекта сцены. 1.1.1) Получаем полигон объекта. 1.1.2) Цикл против часовой стрелки по всем сторонам полигона 1.1.2.1) если сторона «повернута лицом» от источника света, то 1.1.2.1.1) Вычисляем точки полигона тени. 1.1.2.1.2) Рисуем тень на карте теней. 1.1.3) Конец цикла по сторонам полигона. 1.2) Конец цикла по всем непрозрачным объектам. 2) Конец цикла по всем источникам света. 3) Конец Для того, чтобы определить, повернута сторона лицом к источнику или нет, необходимо вычислить скалярное произведение между вектором нормали к стороне и вектором, образованным координатами источника свети и начала вектора нормали. Стороны, повернутые к источнику света, буду давать скалярное произведение с одним знаком, отвернутые от источника – с другим. 17 Алгоритм на Qt: void OptixScene::drawPShadows() { //получаем источники света QList<PLight *> mPLights = getPLights(); if (mPLights.size() == 0) return; //если источников нет //цвет тени QColor shadowCol = shadMap.getColor(); QBrush brush(QColor(shadowCol.red(),shadowCol.green(),shadowCol.blue(), shadowCol.alpha()/mPLights.size())); //для всех источников света QList<Border *> mBorders = getBorders(); for (int k = 0; k < mPLights.size(); ++k) { PLight *ligth = mPLights[k]; QPointF ligPoint = ligth->pos(); //для всех непрозрачных объектов for (int i = 0; i < mBorders.size(); ++i) { QTransform transform = mBorders[i]->sceneTransform(); QPolygonF curPoly = transform.map(mBorders[i]->polygon()); QPointF shPo, shPo2; int polySize = curPoly.size(); 18 //для всех сторон полигона for (int j = 0; j < polySize; ++j) { QPointF curPoint = curPoly[j]; QPointF nextPoint = curPoly[(j+1)%polySize]; QVector2D curToNext(nextPoint.x()-curPoint.x(), nextPoint.y()curPoint.y()); QVector2D normal(curToNext.y(), -1*curToNext.x()); QVector2D lightToCur(curPoint.x()-ligPoint.x(), curPoint.y()ligPoint.y()); //если сторона направлена от источника if ((normal.x()*lightToCur.x() + normal.y()*lightToCur.y()) < 0) { qreal dif = 10*mBorders[i]->getH()/ligth->getH(); shPo.setX(ligPoint.x() + dif*(curPoint.x()-ligPoint.x())); shPo.setY(ligPoint.y() + dif*(curPoint.y()-ligPoint.y())); shPo2.setX(ligPoint.x() + dif*(nextPoint.x()-ligPoint.x())); shPo2.setY(ligPoint.y() + dif*(nextPoint.y()-ligPoint.y())); QPolygonF shadowPolygon; shadowPolygon << curPoint << nextPoint << shPo2 << shPo; QPolygonF shPoly; for (int l = 0; l < shadowPolygon.size(); ++l) { shPoly << shadowPolygon[l] + QPoint(shadMap.getMap()>width()/2, shadMap.getMap()->height()/2); } QPainter p(shadMap.getMap()); p.setPen(Qt::NoPen); p.setBrush(brush); p.drawPolygon(shPoly); } } } } } 19 3.2 - Трассировка луча Этот алгоритм вызывается каждый раз при изменении сцены. Для каждого луча сцены вызывается рекурсивный алгоритм трассировки луча. Трассировка_луча(луч AB) 1) R = бесконечность 2) Цикл для всех объектов сцены. 2.1) Получаем полигон объекта. 2.2) Цикл для всех сторон полигона. 1.2.1) Находим точку пересечения S 1.2.2) Если расстояние AS < R 1.2.2.1) R = |AS| запоминаем расстояние 1.2.2.1) C = S запоминаем точку 2.3) Конец цикла для всех сторон полигона 3) Конец цикла по всем объектам. 4) Рисуем AC 5) Если C принадлежит зеркалу, то 5.1) Отражаем луч (вычисляем новое значение B) 5.2) Вызываем Трассировка_луча(луч СB) 6) Если С принадлежит полупрозрачному объекту, то 6.1) Преломляем луч (вычисляем новое значение B) 6.2) Вызываем Трассировка_луча(луч СB) 7) Конец 20 Диаграмма классов 21 4 Копии экрана программы: 22 Список литературы: 1) Ландсберг Г.С. «Оптика» 2) М. Шлее - Qt 4.5. Профессиональное программирование на C++ (2010) 3) Встроенная справка Qt Assistant 23