Текущая версия |
Ваш текст |
Строка 1: |
Строка 1: |
| [[А.М. Кривцов]] > [[Теоретическая механика: физико-механический факультет|Теоретическая механика]] > [[Курсовые проекты ТМ 2015]] > '''Многочастичный симулятор''' <HR> | | [[А.М. Кривцов]] > [[Теоретическая механика: физико-механический факультет|Теоретическая механика]] > [[Курсовые проекты ТМ 2015]] > '''Многочастичный симулятор''' <HR> |
| | | |
− | [[File:iPhone_image_2015-4-13-1431495674163_1.jpg|800px|right]]
| + | |
| '''''Курсовой проект по [[Теоретическая механика: физико-механический факультет|Теоретической механике]]''''' | | '''''Курсовой проект по [[Теоретическая механика: физико-механический факультет|Теоретической механике]]''''' |
| | | |
| '''Исполнитель:''' [[Старобинский Егор]] | | '''Исполнитель:''' [[Старобинский Егор]] |
| | | |
− | '''Группа:''' [[Группа 09|09]] | + | '''Группа:''' [[Группа 09|09]] (23604) |
| | | |
| '''Семестр:''' весна 2015 | | '''Семестр:''' весна 2015 |
| | | |
| == Аннотация проекта == | | == Аннотация проекта == |
− | Рассматриваемой задачей этого проекта является определение поведения задаваемой механической двухмерной системы<ref>Строго говоря, мы рассматриваем проекцию трёхмерной системы на двухмерное пространство. Так, на изображении в заголовке страницы демонстрируется следующий эксперимент: есть тело в форме параллелепипеда (набор частиц со связями: пружинами), на тело сверху падает стержень. Проекцией этого тела является его слой: треугольная решётка, закреплённая на концах. Проекция стержня - частица (на изображении отсутствует).</ref> из частиц, сил и ограничений. Для этого был создан интернет-сайт с программой, позволяющей найти решение уравнения движения системы (физический движок). Также на базе этой программы был реализован пользовательский интерфейс с возможностями создания и редактирования исходной системы и визуализацией её поведения.
| |
− |
| |
− | В процессе решения широко применялись знания из классической механики и программирования, благодаря чему была реализована возможность моделирования тел из большого числа частиц и ограничений (интерес с точки зрения механики) и консоль управления с упрощёнными интерпретируемыми командами (интерес с точки зрения программирования). Был внедрён ряд математических методов как для вычисления решения уравнения движения, так и для анализа разрешённой системы. Были проведены проверки получившегося движка на предварительно решённых задачах.
| |
− |
| |
− |
| |
− | ''' Научная новизна '''
| |
− |
| |
− | Созданный движок позволяет человеку без специальных знаний в области программирования проводить моделирование собственных систем. Не требуется установки никакого дополнительного софта, программа запускается при помощи браузера как на компьютерах, так и на телефонах, планшетах, телевизорах класса Smart.
| |
− |
| |
− | Визуализация не применяет технологию WebGL, благодаря чему многократно увеличивается диапазон устройств, способных запустить сайт с полноценной функциональностью.
| |
− |
| |
− | Применён метод нахождения периода движения частицы по участку траектории путём последовательного разбиения поля на сетки с возрастающей плотностью ячеек (идея позаимствована из теории слов).
| |
− |
| |
− |
| |
− |
| |
− | Движок представляет все тела как набор частиц со связями и применяет базовый двухшаговый метод численного интегрирования Верле и другие математические методы для разрешения уравнения движения в короткое время.
| |
| | | |
− | По возможности упрощено интегрирование в систему новых алгоритмов анализа (требует знаний в программировании на javascript как для реализации алгоритма, так и для его интегрирования), при этом все пользователи оперируют только актуальной версией программы.
| |
− |
| |
− |
| |
− | Объединение физического движка и пользовательского интерфейса получило название "Многочастичный симулятор", имеет открытый исходный код и выполнено без использования готовых решений по теме проекта.
| |
| | | |
| == Формулировка задачи == | | == Формулировка задачи == |
− |
| |
− | ===== Цель работы =====
| |
| Создание интернет-сайта, позволяющего пользователю моделировать многоточечную систему онлайн. | | Создание интернет-сайта, позволяющего пользователю моделировать многоточечную систему онлайн. |
− |
| |
− | ===== Решаемые задачи =====
| |
− | # решение уравнения движения;
| |
− | # визуализация.
| |
| | | |
| == Общие сведения по теме == | | == Общие сведения по теме == |
− | | + | Применяется двухшаговый [[Интегрирование Верле|метод численного интегрирования Верле]] и метод итераций. |
− | ===== Уравнение движения =====
| + | Язык реализации: javascript. |
− | Пусть мы наблюдаем тело в момент времени <math>t</math>.
| |
− | | |
− | Хотим знать, где окажется тело через малое изменение времени - <math>\Delta t</math>.
| |
− | | |
− | Рассмотрим базовый метод интегрирования Верле:
| |
− | | |
− | <big><math>\vec{x}(t + \Delta t) = 2\vec{x}(t) - \vec{x}(t - \Delta t) +\frac{ \vec{R}(t) \Delta t^2 }m</math></big>, где
| |
− | | |
− | <math>\vec{x}</math> - позиция точки,
| |
− | | |
− | <math>\vec{R}</math> - равнодействующая всех сил, действующих на тело,
| |
− | | |
− | <math>m</math> - масса тела,
| |
− | | |
− | <math>t</math> - текущий момент времени,
| |
− | | |
− | <math>\Delta t</math> - малое изменение времени.
| |
− | | |
− | Метод Верле позволяет вычислять траекторию по упрощённой схеме: зная предыдущее и текущее положения (<math>\vec{x}(t - \Delta t)</math> и <math>\vec{x}(t)</math> соответственно) и мгновенное значение равнодействующей приложенных сил в текущем положении <math>\vec{R}(t)</math>.
| |
− | | |
− | Достоинства метода: самокоррекция и бóльшая точность по сравнению с численным методом Эйлера.
| |
− | | |
− | Язык реализации: JavaScript. | |
− | | |
− | | |
− | ===== Визуализация =====
| |
− | Язык рализации: pure SCSS.
| |
− | | |
− | Обработка событий: JavaScript.
| |
− | | |
− | Манипуляции с DOM: jQuery (безболезненно заменяется на Zepto).
| |
− | | |
− | Отказ от WebGL продиктован выбором методов оптимизации для возможности работы с тысячами частиц.
| |
| | | |
| == Решение == | | == Решение == |
| | | |
| | | |
− | ===== Результат ===== | + | == Обсуждение результатов и выводы == |
− | <center>
| |
− | {{#widget:Iframe|url=https://ailurus.ru/stands/phen/|width=960|height:540|border=0}}
| |
| | | |
− | <big>[//ailurus.ru/stands/phen/ Страница решения]</big>
| |
− | </center>
| |
| | | |
− | Вы можете попробовать возможности симулятора прямо здесь, либо перейдя на полноразмерную страницу решения.
| |
− |
| |
− | В пункте '''4.7''' представлен пример готовой команды для создания трёхатомной молекулы.
| |
− |
| |
− | [[File:iPhone_image_2015-4-13-1431495673979_0.jpg|thumb|Нахождение периода в простом движении]]
| |
− |
| |
− | ===== Элементы системы =====
| |
− | * Частицы;
| |
− | * Стержни и пружины<ref>Стержни рассчитываются на расстяжение/сжатие методом коррекции координат.<br>
| |
− | Действие пружин учитывается как действие сил упругости.</ref>;
| |
− | * Стенки;
| |
− | * Поле сил;
| |
− | * Рабочее окно;
| |
− | * Сетки разметки;
| |
− | * Консоль;
| |
− | * Плеер.
| |
− |
| |
− |
| |
− | [[File:iPhone_image_2015-4-13-1431495674173_2.jpg|thumb|Пример вывода консоли]]
| |
− |
| |
− |
| |
− | ===== Возможности консоли =====
| |
− | * Конфигурация начальной системы тел;
| |
− | * Изменение системы в процессе работы ("на лету");
| |
− | * Запуск алгоритмов анализа системы;
| |
− | * Распознавание и вывод ошибок в пользовательских запросах и в исходном коде;
| |
− | * Распознавание и вывод предупреждений в пользовательских запросах и в исходном коде;
| |
− | * Подключение/отключение сеток разметки, в том числе с пользовательскими размерами ячейки;
| |
− | * Тетрис.
| |
− |
| |
− |
| |
− | ===== Команды консоли =====
| |
− | Координаты пишутся в декартовой системе (х,у), единица измерения - пиксели, орт х направлен от левого края к правому, орт у от верхнего края к нижнему. Пример: (0,100) - координаты точки, лежащей на левом краю экрана на 100 пикселей ниже верхней границы.
| |
− |
| |
− | Консоль выводит каждое сообщение как одну строку. Если доступной длины строки не хватает, сообщение обрезается. Для отображения полной версии сообщения необходимо кликнуть по нему мышью.
| |
− |
| |
− |
| |
− | <div class="mw-collapsible mw-collapsed" style="width:100%" >
| |
− | <big>'''Примеры основных запросов'''</big>
| |
− | <div class="mw-collapsible-content">
| |
− | {{начало цитаты}}
| |
− | Очистить поле консоли
| |
− |
| |
− | * '''clrscr'''
| |
− |
| |
− |
| |
− | Отобразить статистику элементов системы и число тиков:
| |
− |
| |
− | * '''getInfo'''
| |
− |
| |
− |
| |
− | Создать частицу
| |
− |
| |
− | * '''addPoint (100,100)''' (0,10) 80 5<ref name="optValues">Без выделения жирным написаны необязательные параметры. При желании указать необязательный параметр все значения слева от него следует считать обязательными (во избежание путаницы при парсинге безразмерных величин в команде).</ref><ref>Значения по умолчанию: вектор скорости нулевой, радиус равен 50 пикселям, масса равна 5 у. е.</ref>, где
| |
− |
| |
− | (100,100) - текущие координаты
| |
− |
| |
− | (0,10) - вектор скорости относительно начала координат
| |
− |
| |
− | 80 - радиус частицы в пикселях
| |
− |
| |
− | 5 - масса частицы в у. е.
| |
− |
| |
− |
| |
− | Задать вектор скорости (относительно начала координат)
| |
− |
| |
− | * '''setVelocity #0 (10,10)''', где
| |
− |
| |
− | 0 - id частицы<ref name="pointsId">Идентификационный номер элемента в системе. Генерируется последовательно, начиная с нуля, для стенок, стержней, пружин и частиц при их добавлении. Для частиц значение id можно найти нажатием сначала на кнопку "Get point's id", а затем на частицу: тогда Id отобразится в консоли.</ref>
| |
− |
| |
− | (10,10) - новый вектор скорости относительно начала координат
| |
− |
| |
− |
| |
− | Переместить частицу (относительно начала координат)
| |
− |
| |
− | * '''movePoint #3 (100,100)''' saveV<ref name="optValues" /><ref name="workWithVisualisation">'''Важно!''' Визуально действие этой команды применится только при перерисовке кадра.</ref>, где
| |
− |
| |
− | 3 - id частицы<ref name="pointsId" />
| |
− |
| |
− | (100,100) - новые координаты
| |
− |
| |
− | saveV - флаг сохранения скорости. Если указан, частица после перемещения сохранит вектор своей скорости.
| |
− |
| |
− |
| |
− | Задать массу
| |
− |
| |
− | * '''setProp #0 mass 10''', где
| |
− |
| |
− | 0 - id частицы<ref name="pointsId" />
| |
− |
| |
− | 10 - новая масса в у. е.
| |
− |
| |
− | * '''setProp #0 invmass 0.1''', где
| |
− |
| |
− | 0 - id частицы<ref name="pointsId" />
| |
− |
| |
− | 0.1 - обратное значение новой массы
| |
− |
| |
− |
| |
− | Задать радиус
| |
− |
| |
− | * '''setProp #0 radius 100''', где
| |
− |
| |
− | 0 - id частицы<ref name="pointsId" />
| |
− |
| |
− | 100 - радиус частицы в пикселях
| |
− |
| |
− |
| |
− | Создать пружину между частицами
| |
− |
| |
− | * '''addSpring #0 #1''' 50<ref name="optValues" /><ref name="workWithVisualisation" /><ref>Жёсткость пружины по умолчанию равна 5 у. е.</ref>, где
| |
− |
| |
− | 0, 1 - id частиц<ref name="pointsId" />
| |
− |
| |
− | 50 - жёсткость пружины в у. е.
| |
− |
| |
− |
| |
− | Изменить жёсткость пружины
| |
− | * '''changeSpring #5 10''', где
| |
− |
| |
− | 5 - id пружины<ref name="pointsId" />
| |
− |
| |
− | 10 - новая жёсткость в у. е.
| |
− |
| |
− |
| |
− | Изменить жёсткость одинаковых пружин
| |
− | * '''changeSprings 10->15''', где
| |
− |
| |
− | 10 - текущая жёсткость в у. е.
| |
− |
| |
− | 15 - новая жёсткость в у. е.
| |
− |
| |
− |
| |
− | Изменить жёсткость всех пружин
| |
− | * '''changeSprings all->20''', где
| |
− |
| |
− | 20 - новая жёсткость в у. е.
| |
− |
| |
− |
| |
− | Создать стержень между частицами
| |
− |
| |
− | * '''addStick #2 #1'''<ref name="workWithVisualisation" />, где
| |
− |
| |
− | 2, 1 - id частиц<ref name="pointsId" />
| |
− |
| |
− |
| |
− | Отключение гравитации
| |
− |
| |
− | * '''gravity disable'''
| |
− |
| |
− |
| |
− | Задать вектор ускорения свободного падения (относительно начала координат)
| |
− |
| |
− | * '''gravity (0,10)'''
| |
− |
| |
− |
| |
− | Отобразить сетку<ref>''' Важно! ''' Отображается максимум одна таблица за раз.</ref>
| |
− |
| |
− | * '''showGrid type 0'''
| |
− |
| |
− | Сетка 100х50 пикселей.
| |
− |
| |
− | * '''showGrid type 1'''
| |
− |
| |
− | Сетка 50х25 пикселей.
| |
− |
| |
− | * '''showGrid type 2'''
| |
− |
| |
− | Сетка 20х10 пикселей.
| |
− |
| |
− | * '''showGrid 100x75''', где
| |
− |
| |
− | 100 - ширина ячеек в пикселях
| |
− |
| |
− | 50 - высота ячеек в пикселях
| |
− |
| |
− |
| |
− | Спрятать сетку
| |
− |
| |
− | * '''hideGrid'''
| |
− |
| |
− |
| |
− | Запустить симуляцию
| |
− | * '''play'''
| |
− |
| |
− |
| |
− | Остановить симуляцию
| |
− | * '''stop'''
| |
− |
| |
− |
| |
− | "Промотать" симуляцию
| |
− | * '''step(100)''', где
| |
− |
| |
− | 100 - число пропускаемых отрисовкой тиков
| |
− |
| |
− |
| |
− | Исполнить внутренний метод
| |
− | * '''execute nameMethod(params)''', где
| |
− |
| |
− | nameMethod - название метода
| |
− |
| |
− | params - сообщаемые параметры
| |
− |
| |
− |
| |
− | Посмотреть историю запросов
| |
− | * '''getHistory''' <ref>Запросы выводятся в всплывающем окне одной цельной командой, для воспроизведения цепочки запросов достаточно ввести эту команду в консоль.
| |
| <br> | | <br> |
− | '''Важно!''' Учитываются только запросы, успешно введённые в консоль. Воздействия на систему при помощи кнопки "Move point" будут потеряны.</ref>
| + | Скачать отчет: |
− | | + | <br> |
− | | + | Скачать презентацию: |
− | Объединить команды в одном запросе
| |
− | * '''commandOne---commandTwo''', где
| |
− | | |
− | commandOne, commandTwo - команды консоли, могут также состоять из объединённых команд
| |
− | {{конец цитаты}}
| |
− | </div>
| |
− | </div>
| |
− | | |
− | ===== Возможности плеера =====
| |
− | * Воспроизведение/пауза симуляции с заданным <math>\Delta t</math>;
| |
− | * Скачок вперёд на кратное <math>\Delta t</math> время;
| |
− | * "Замедление времени"<ref>При малой производительности клиента уменьшаем число отрисовок в единицу времени для сохранения гладкости анимации. Управляется через консоль.</ref>.
| |
− | | |
− | ===== Кнопки интерфейса =====
| |
− | * Кнопка '''Get point's id'''
| |
− | | |
− | После нажатия на кнопку, а затем на частицу выводит id последней в консоль.
| |
− | | |
− | | |
− | * Кнопка '''Move point'''<ref name="workWithVisualisation" />
| |
− | | |
− | Эквивалентна команде консоли movePoint без флага saveV.
| |
− | | |
− | После нажатия на кнопку, а затем на частицу закрепляет управление положением последней за курсором мыши.
| |
− | | |
− | Дальнейшие нажатия на свободные участки поля переносят эту частицу в точку нажатия, при этом её скорость считается нулевой.
| |
− | | |
− | Для прекращения управления следует вновь нажать исходную кнопку.
| |
− | | |
− | | |
− | * Клавиша клавиатуры '''~''' (также '''`''', '''ё''', '''Ё''')
| |
− | | |
− | Эквивалентна командам консоли play/stop и кнопкам <big>'''►'''</big> / <big>'''| |'''</big> плеера.
| |
− | | |
− | | |
− | * Кнопки плеера
| |
− | | |
− | <big>'''►'''</big> / <big>'''| |'''</big> - воспроизведение/остановка симуляции (эквивалентна командам консоли play/stop).
| |
− | | |
− | '''►''' - переход на тик вперёд (эквивалентна команде консоли step(1)).
| |
− | | |
− | '''►►''' - переход на 50 тиков вперёд (эквивалентна команде консоли step(50)).
| |
− | | |
− | '''►|''' - переход на 100 тиков вперёд (эквивалентна команде консоли step(100)).
| |
− | | |
− | ===== Пример системы =====
| |
− | Смоделируем молекулу из трёх атомов (подобную молекуле углекислого газа, рассматриваемой в [[КП: Молекула углекислого газа|курсовом проекте]] [[Смирнов Александр|А. Смирнова]]).
| |
− | | |
− | [[File:phenCO2Example.png|center]]
| |
− | | |
− | Для создания такой системы необходимо последовательно выполнить следующие команды:
| |
− | | |
− | {{начало цитаты}}
| |
− | Создаём атомы кислорода, углерода и кислорода соответственно.
| |
− | | |
− | * addPoint (100,100) (0,0) 40 5
| |
− | | |
− | * addPoint (250,100) (0,0) 50 10
| |
− | | |
− | * addPoint (400,100) (0,0) 40 5
| |
− | | |
− | Соединяем атомы пружинами для моделирования связей. Первая пружина обеспечивает устойчивость молекулы, не создавая внутреннего напряжения.
| |
− | | |
− | * addSpring #0 #2 10
| |
− | | |
− | * addSpring #0 #1 20
| |
− | | |
− | * addSpring #1 #2 20
| |
− | | |
− | Дополнительно можем задать скорости у атомов кислорода для создания колебаний.
| |
− | | |
− | * setVelocity #0 (1,0)
| |
− | | |
− | * setVelocity #2 (-1,0)
| |
− | {{конец цитаты}}
| |
− | | |
− | Итоговый код (одна строка):
| |
− | | |
− | {{начало цитаты}}
| |
− | addPoint (100,100) (0,0) 40 5---addPoint (250,100) (0,0) 50 10---addPoint (400,100) (0,0) 40 5---addSpring #0 #2 10---addSpring #0 #1 20---addSpring #1 #2 20---setVelocity #0 (1,0)---setVelocity #2 (-1,0)
| |
− | {{конец цитаты}}
| |
− | | |
− | == Выдержки кода решения ==
| |
− | <div class="mw-collapsible mw-collapsed" style="width:100%" >
| |
− | '''Исходный код некоторых файлов [HTML, JS, CSS]'''
| |
− | <div class="mw-collapsible-content">
| |
− | Файл '''"point.js"'''
| |
− | <syntaxhighlight lang="javascript" line start="1" enclose="div">
| |
− | function Point(mass, radius, coorXY, oldCoorXY, isExpToPot)
| |
− | {
| |
− | this.mass = mass;
| |
− | this.invmass = 1 / mass;
| |
− | this.radius = radius;
| |
− | this.coor = coorXY;
| |
− | this.oldCoor = oldCoorXY || coorXY;
| |
− | this.id = getId();
| |
− | this.type = 'point';
| |
− | this.isExpToPot = isExpToPot || false;
| |
− |
| |
− | // log
| |
− | console.log('Create: '+this.toString());
| |
− | }
| |
− | | |
− | | |
− | Point.prototype.checkCoor = function(){
| |
− | if (this.coor.isNaN())
| |
− | {
| |
− | console.error('Coor is NaN: ' + this);
| |
− | player.stop();
| |
− | }
| |
− | else
| |
− | {
| |
− | if (this.oldCoor.isNaN())
| |
− | {
| |
− | console.warning('OldCoor is NaN: ' + this);
| |
− | this.oldCoor = new Coor(this.coor);
| |
− | }
| |
− | else
| |
− | {
| |
− | return ;
| |
− | }
| |
− | }
| |
− | }
| |
− | | |
− | Point.prototype.toString = function(type){
| |
− | if (type === 'full')
| |
− | {
| |
− | return JSON.stringify(this, [
| |
− | 'type',
| |
− | 'id',
| |
− | 'coor',
| |
− | 'mass_',
| |
− | 'radius_',
| |
− | 'x',
| |
− | 'y',
| |
− | 'begin',
| |
− | 'end'
| |
− | ], 4)
| |
− | }
| |
− | else
| |
− | {
| |
− | return JSON.stringify(this, [
| |
− | 'type',
| |
− | 'id',
| |
− | 'coor',
| |
− | 'x',
| |
− | 'y',
| |
− | 'begin',
| |
− | 'end'
| |
− | ], 4)
| |
− | }
| |
− | }
| |
− | | |
− | Point.prototype.move = function(coorXY){
| |
− | var delta = minusCoor(this.coor, this.oldCoor);
| |
− | this.coor = coorXY;
| |
− | this.oldCoor = minusCoor(coorXY, delta);
| |
− | // log
| |
− | console.log('Move: '+this.toString());
| |
− | }
| |
− | | |
− | Point.prototype.moveEase = function(coorXY){
| |
− | this.coor = coorXY;
| |
− | this.oldCoor = coorXY;
| |
− | // log
| |
− | console.log('Move: '+this.toString());
| |
− | }
| |
− | </syntaxhighlight>
| |
− | | |
− | Файл '''"player.js"'''
| |
− | <syntaxhighlight lang="javascript" line start="1" enclose="div">
| |
− | var step;
| |
− | | |
− | function Player(dt, phys, render, console)
| |
− | {
| |
− | this.tick = 0;
| |
− | this.dt = dt;
| |
− | this.tickCount = 0;
| |
− | this.physics = new Physics(phys, this);
| |
− | this.renderer = new Renderer(render, this);
| |
− | this.parser = new Parser(this);
| |
− | this.console = new Console(console, this);
| |
− | var checker = checkerPl.bind(this);
| |
− |
| |
− | this.stop = function(){
| |
− | clearTimeout(this.tick);
| |
− | this.tick = 0;
| |
− |
| |
− | // log
| |
− | this.console.logAns('Pause...');
| |
− | }
| |
− |
| |
− | this.play = function(){
| |
− | if (!this.tick)
| |
− | {
| |
− | this.tick = setTimeout(function stepTm(pl){
| |
− | step(pl, checker);
| |
− | pl.tick = setTimeout(stepTm, pl.dt, pl);
| |
− | }, this.dt, this);
| |
− | // log
| |
− | this.console.logAns('Play');
| |
− | }
| |
− | }
| |
− |
| |
− | this.shifter = function(){
| |
− | if (this.tick)
| |
− | {
| |
− | this.stop();
| |
− |
| |
− | return 0;
| |
− | }
| |
− | else
| |
− | {
| |
− | this.play();
| |
− |
| |
− | return 1;
| |
− | }
| |
− | }
| |
− |
| |
− | this.nextStep = function(i){
| |
− | if (!i)
| |
− | {
| |
− | step(this, checker);
| |
− | // log
| |
− | this.console.logAns('step');
| |
− | }
| |
− | else
| |
− | {
| |
− | this.console.logAns('step x ' + i);
| |
− |
| |
− | while (i--)
| |
− | {
| |
− | step(this, checker);
| |
− | }
| |
− | }
| |
− |
| |
− | }
| |
− |
| |
− | this.getInfo = function(){
| |
− | this.console.logAns('Tick count: '+this.tickCount);
| |
− | this.console.logAns('Points count: '+this.physics.Points.length);
| |
− | this.console.logAns('Walls count: '+this.physics.Walls.length);
| |
− | this.console.logAns('Springs count: '+this.physics.Springs.length);
| |
− | this.console.logAns('Sticks count: '+this.physics.Sticks.length);
| |
− | }
| |
− |
| |
− | this.find = function(id){
| |
− | if (id in this.renderer.arr)
| |
− | {
| |
− | return this.renderer.arr[id].ptr;
| |
− | }
| |
− |
| |
− | return 0;
| |
− | }
| |
− |
| |
− | this.addPoint = function(mass, radius, coorXY, oldCoorXY, isExpToPot){
| |
− | var tmp = this.physics.addPoint(mass, radius, coorXY, oldCoorXY, isExpToPot);
| |
− | this.renderer.addPoint(tmp);
| |
− | // log
| |
− | this.console.logAns('Create point.');
| |
− | }
| |
− |
| |
− | this.addWall = function(vectM, vectO){
| |
− | var tmp = this.physics.addWall(vectM, vectO);
| |
− | this.renderer.addWall(tmp);
| |
− | // log
| |
− | this.console.logAns('Create wall.');
| |
− | }
| |
− |
| |
− | this.addStick = function(elem1, elem2){
| |
− | var tmp = this.physics.addStick(elem1, elem2);
| |
− | this.renderer.addStick(tmp);
| |
− | // log
| |
− | this.console.logAns('Create stick.');
| |
− | }
| |
− |
| |
− | this.addSpring = function(elem1, elem2, k){
| |
− | var tmp = this.physics.addSpring(elem1, elem2, k);
| |
− | this.renderer.addSpring(tmp);
| |
− | // log
| |
− | this.console.logAns('Create spring.');
| |
− | }
| |
− |
| |
− | this.parse = function(str){
| |
− | return this.parser.parse(str);
| |
− | }
| |
− |
| |
− | this.log = function(str){
| |
− | this.console.log(str);
| |
− | }
| |
− |
| |
− | this.warn = function(str){
| |
− | this.console.warn(str);
| |
− | }
| |
− |
| |
− | this.error = function(str){
| |
− | this.console.error(str);
| |
− | }
| |
− | }
| |
− | | |
− | step = function(pl, checker){
| |
− | if (!checker())
| |
− | {
| |
− | pl.physics.step();
| |
− | pl.renderer.draw();
| |
− | pl.tickCount++;
| |
− | }
| |
− | else
| |
− | {
| |
− | pl.stop();
| |
− | checker('delete');
| |
− | }
| |
− | }
| |
− | | |
− | function checkerPl(mode, vr, val)
| |
− | {
| |
− | var st = checkerPl.state;
| |
− |
| |
− | if (arguments.length == 0) // check
| |
− | {
| |
− | for (var i in st)
| |
− | {
| |
− | if (player[st[i].vr] < st[i].val)
| |
− | {
| |
− | return 1;
| |
− | }
| |
− | }
| |
− |
| |
− | return 0;
| |
− | }
| |
− |
| |
− | if (mode == 'delete') // delete
| |
− | {
| |
− | for (var i in st)
| |
− | {
| |
− | if (player[st[i].vr] < st[i].val)
| |
− | {
| |
− | delete st[i];
| |
− | }
| |
− | }
| |
− |
| |
− | return 0;
| |
− | }
| |
− |
| |
− | if (mode == 'add') // add
| |
− | {
| |
− | st.push({'vr': vr, 'val': val});
| |
− |
| |
− | return 0;
| |
− | }
| |
− | }
| |
− | </syntaxhighlight>
| |
− | | |
− | Файл '''"renderer.js"'''
| |
− | <syntaxhighlight lang="javascript" line start="1" enclose="div">
| |
− | function Renderer(param, pl)
| |
− | {
| |
− | this.scr = $('#'+param.idEl);
| |
− | this.borderSize = param.borderSize;
| |
− | this.arr = new Array();
| |
− | this.player = pl;
| |
− | }
| |
− | | |
− | Renderer.prototype.addPoint = function(point){
| |
− | var el = $('<div data-id="'+point.id+'" class="point"></div>');
| |
− | el.css('width', 2 * point.radius - 2 * this.borderSize);
| |
− | el.css('height', 2 * point.radius - 2 * this.borderSize);
| |
− | el.css('border-radius', 2 * point.radius - 2 * this.borderSize);
| |
− | el.css('font-size', 10 * (point.radius - this.borderSize) + '%');
| |
− | this.scr.append(el);
| |
− |
| |
− | var tmp = {
| |
− | 'el': el,
| |
− | 'ptr': point,
| |
− | 'draw': function(){
| |
− | var c = this.patch(this.ptr.coor);
| |
− | this.el.css('top', c.y);
| |
− | this.el.css('left', c.x);
| |
− | },
| |
− | 'setCoor': function(c){
| |
− | c = c || this.patch(this.ptr.coor);
| |
− | this.el.css('top', c.y);
| |
− | this.el.css('left', c.x);
| |
− | },
| |
− | 'patch': function(c){
| |
− | return new Coor(c.x - this.ptr.radius, c.y - this.ptr.radius);
| |
− | },
| |
− | 'patchC': function(c){
| |
− | return new Coor(c.x + this.ptr.radius, c.y + this.ptr.radius);
| |
− | }
| |
− | };
| |
− | this.arr[point.id] = tmp;
| |
− |
| |
− | tmp.setCoor();
| |
− | }
| |
− | | |
− | | |
− | Renderer.prototype.log = function(str){
| |
− | this.player.log(str);
| |
− | }
| |
− | | |
− | | |
− | Renderer.prototype.warn = function(str){
| |
− | this.player.warn(str);
| |
− | }
| |
− | | |
− | | |
− | Renderer.prototype.error = function(str){
| |
− | this.player.error(str);
| |
− | }
| |
− | | |
− | | |
− | Renderer.prototype.addWall = function(wall){
| |
− | var el = $('<div data-id="'+wall.id+'" class="wall"></div>');
| |
− | el.css('left', wall.main_.midX());
| |
− | el.css('top', wall.main_.midY())
| |
− | this.scr.append(el);
| |
− |
| |
− | var tmp = {
| |
− | 'el': el,
| |
− | 'ptr': wall
| |
− | }
| |
− |
| |
− | this.arr[wall.id] = tmp;
| |
− | }
| |
− | | |
− | Renderer.prototype.addStick = function(stick){
| |
− | var el = $('<div data-id="'+stick.id+'" class="stick"></div>');
| |
− | this.scr.append(el);
| |
− |
| |
− | var tmp = {
| |
− | 'el': el,
| |
− | 'ptr': stick,
| |
− | 'setCoor': function(b, e, l){
| |
− | setCoorLink.call(this, b, e, l);
| |
− | },
| |
− | 'draw': function(){
| |
− | this.setCoor(this.ptr.coorB, this.ptr.coorE, this.ptr.length_);
| |
− | }
| |
− | };
| |
− | | |
− | this.arr[stick.id] = tmp;
| |
− | }
| |
− | | |
− | Renderer.prototype.addSpring = function(spring){
| |
− | var el = $('<div data-id="'+spring.id+'" class="spring"></div>');
| |
− | this.scr.append(el);
| |
− |
| |
− | var tmp = {
| |
− | 'el': el,
| |
− | 'ptr': spring,
| |
− | 'setCoor': function(b, e, l){
| |
− | setCoorLink.call(this, b, e, l);
| |
− | },
| |
− | 'draw': function(){
| |
− | if (this.ptr.k == 0)
| |
− | {
| |
− | this.el.hide();
| |
− | delete this.draw;
| |
− | }
| |
− | else
| |
− | {
| |
− | this.setCoor(this.ptr.coorB, this.ptr.coorE, length(this.ptr.coorB, this.ptr.coorE));
| |
− | }
| |
− | }
| |
− | };
| |
− | | |
− | this.arr[spring.id] = tmp;
| |
− | }
| |
− | | |
− | Renderer.prototype.draw = function(){
| |
− | for (var key in this.arr)
| |
− | {
| |
− | var l = this.arr[key];
| |
− |
| |
− | if (l.draw)
| |
− | {
| |
− | l.draw();
| |
− | }
| |
− | | |
− | }
| |
− | }
| |
− | </syntaxhighlight>
| |
− | | |
− | Файл '''"func.js"'''
| |
− | <syntaxhighlight lang="javascript" line start="1" enclose="div">
| |
− | function Coor(e, y)
| |
− | {
| |
− | if (e.pageX)
| |
− | {
| |
− | this.x = e.pageX;
| |
− | this.y = e.pageY;
| |
− |
| |
− | return ;
| |
− | }
| |
− | if (typeof e == 'number')
| |
− | {
| |
− | this.x = e;
| |
− | this.y = y;
| |
− |
| |
− | return ;
| |
− | }
| |
− | else
| |
− | {
| |
− | this.x = e.x;
| |
− | this.y = e.y;
| |
− |
| |
− | return ;
| |
− | }
| |
− | }
| |
− | | |
− | function createHTMLTable(id, numberOfCellsPerRow, numberOfRows, cellWidth, cellHeight)
| |
− | {
| |
− | var generatedHTMLTable = '<style> \
| |
− | #table' + id + '.show{} \
| |
− | #table' + id + '.hide{display:none;} \
| |
− | #table' + id + '{display:table;position:fixed;width: 2000px;height:1000px;top:27px;border-collapse:collapse;left:0px;z-index:1;outline:2px solid gray;} \
| |
− | #table' + id + ' > div{display:table-row;} \
| |
− | #table' + id + ' > div > div{display:table-cell;width:' + cellWidth + 'px;height:' + cellHeight + 'px;outline:1px solid white;} \
| |
− | </style>';
| |
− | for(var ii = 0; ii < numberOfRows; ++ii)
| |
− | {
| |
− | generatedHTMLTable += "<div>";
| |
− |
| |
− | for(var i = 0; i < numberOfCellsPerRow; ++i)
| |
− | {
| |
− | generatedHTMLTable += "<div></div>";
| |
− | }
| |
− |
| |
− | generatedHTMLTable += "</div>";
| |
− | }
| |
− |
| |
− | var tableDiv = $('<div id="table'+id+'" class="show" data-type="grid">'+generatedHTMLTable+'</div>')
| |
− |
| |
− | $('body').append(tableDiv);
| |
− | }
| |
− | | |
− | function colorHTMLTableCell(id, numberOfRows, numberOfCell)
| |
− | {
| |
− | var fix = 1;
| |
− | var rowBefore = Math.floor((fix + numberOfCell) / numberOfRows);
| |
− | var styleToColorDiv = $('<style>#table' + id + ' > div:nth-child(' + (rowBefore + 1 + fix) + ') > div:nth-child(' + (0 + Math.floor(fix + numberOfCell - rowBefore * numberOfRows)) +'){outline:3px dashed dodgerblue;}</style>');
| |
− | $('#table' + id).append(styleToColorDiv);
| |
− | }
| |
− | | |
− | | |
− | Coor.prototype.toString = function(type){
| |
− | return JSON.stringify(this);
| |
− | }
| |
− | | |
− | Coor.prototype.plus = function(B){
| |
− | this.x += B.x;
| |
− | this.y += B.y;
| |
− |
| |
− | return this;
| |
− | }
| |
− | | |
− | Coor.prototype.isNaN = function(){
| |
− | if (isNaN(this.x) || isNaN(this.y))
| |
− | return 1;
| |
− | return 0;
| |
− | }
| |
− | | |
− | function dot(A, B)
| |
− | {
| |
− | return (A.x * B.x + A.y * B.y);
| |
− | }
| |
− | | |
− | function length(A, B)
| |
− | {
| |
− | return Math.sqrt((A.x - B.x)*(A.x - B.x) + (A.y - B.y)*(A.y - B.y));
| |
− | }
| |
− | | |
− | function sign(x)
| |
− | {
| |
− | return (x > 0 ? 1 : x < 0 ? -1 : 0);
| |
− | }
| |
− |
| |
− | function Vector(A, B)
| |
− | {
| |
− | this.begin = A;
| |
− | this.end = B;
| |
− | this.length = length(A, B);
| |
− | }
| |
− | | |
− | Vector.prototype.midX = function(){
| |
− | return (this.begin.x + this.end.x)/2;
| |
− | }
| |
− | | |
− | | |
− | Vector.prototype.midY = function(){
| |
− | return (this.begin.y + this.end.y)/2;
| |
− | }
| |
− | | |
− | Vector.prototype.toString = function(type){
| |
− | if (type === 'full')
| |
− | {
| |
− | return JSON.stringify(this, null, 4)
| |
− | }
| |
− | else
| |
− | {
| |
− | return JSON.stringify(this, [
| |
− | 'begin',
| |
− | 'end',
| |
− | 'length',
| |
− | 'x',
| |
− | 'y',
| |
− | ], 4)
| |
− | }
| |
− | }
| |
− | | |
− | Vector.prototype.resize = function(){
| |
− | this.length = length(A, B);
| |
− | }
| |
− | | |
− | Vector.prototype.plus = function(B){
| |
− | this.begin.plus(B.begin);
| |
− | this.end.plus(B.end);
| |
− | this.resize();
| |
− |
| |
− | return this;
| |
− | }
| |
− | | |
− | function plusVector(A, B)
| |
− | {
| |
− | return new Vector(plusCoor(A.begin, B.begin), plusCoor(A.end, B.end));
| |
− | }
| |
− | | |
− | function plusCoor(A, B)
| |
− | {
| |
− | return new Coor(A.x + B.x, A.y + B.y);
| |
− | }
| |
− |
| |
− | function minusCoor(A, B)
| |
− | {
| |
− | return new Coor(A.x - B.x, A.y - B.y);
| |
− | }
| |
− | | |
− | function timesCoor(e, k)
| |
− | {
| |
− | return new Coor(k * e.x, k * e.y);
| |
− | }
| |
− | function deltaCoor(e)
| |
− | {
| |
− | return Math.sqrt(dot(e,e));
| |
− | }
| |
− | | |
− | function setCoorLink(a, b, length)
| |
− | {
| |
− |
| |
− | this.el.css('top', a.y);
| |
− | this.el.css('left', a.x);
| |
− | this.el.css('height', length);
| |
− | var angle = 0;
| |
− | var deltaX = b.x - a.x;
| |
− | var deltaY = b.y - a.y;
| |
− |
| |
− | if (deltaX == 0)
| |
− | angle = ((deltaY > 0) - (deltaY < 0)) * Math.PI / 2;
| |
− | else
| |
− | if(deltaX < 0)
| |
− | angle = ((deltaY >= 0) - (deltaY < 0)) * Math.PI + Math.atan(deltaY / deltaX);
| |
− | else angle = Math.atan(deltaY / deltaX);
| |
− | | |
− | angle -= Math.PI/2;
| |
− | this.el.css('transform', 'rotate(' + angle + 'rad)');
| |
− | }
| |
− | </syntaxhighlight>
| |
− | | |
− | Файл '''"console.js"'''
| |
− | <syntaxhighlight lang="javascript" line start="1" enclose="div">
| |
− | function Console(par, pl)
| |
− | {
| |
− | this.player = pl;
| |
− | this.btn = $(par.btn);
| |
− | this.input = $(par.inp);
| |
− | this.txt = $(par.log);
| |
− | this.history = new Array();
| |
− | this.oHistory = new Array();
| |
− | var exec = this.execute.bind(this);
| |
− | var keydown = keydownCons.bind(this);
| |
− |
| |
− | this.btn.click(function(){
| |
− | exec();
| |
− | });
| |
− |
| |
− | this.input.keydown(keydown);
| |
− | }
| |
− | | |
− | function keydownCons(e)
| |
− | {
| |
− | var e = e || window.event;
| |
− | var key = e.which;
| |
− |
| |
− | if (key == 38)
| |
− | {
| |
− | this.input.val(this.getHist());
| |
− | }
| |
− |
| |
− | if (key == 40)
| |
− | {
| |
− | this.input.val(this.getHistInv());
| |
− | }
| |
− |
| |
− | if (e.ctrlKey && key == 13)
| |
− | {
| |
− | this.input.val(this.input.val() + '---');
| |
− |
| |
− | return ;
| |
− | }
| |
− |
| |
− | if (key == 13)
| |
− | {
| |
− | this.btn.trigger('click');
| |
− | }
| |
− | }
| |
− | | |
− | Console.prototype.log = function(str){
| |
− | var el = $('<div></div>').addClass('log').html(str.toString().trim()).click(showAllLog);
| |
− |
| |
− | this.txt.prepend(el);
| |
− | };
| |
− | | |
− | function showAllLog()
| |
− | {
| |
− | $(this).addClass('all');
| |
− | }
| |
− | | |
− | Console.prototype.warn = function(str){
| |
− | var el = $('<div></div>').addClass('warn').html(str.toString().trim()).click(showAllLog);
| |
− |
| |
− | this.txt.prepend(el);
| |
− | };
| |
− | | |
− | Console.prototype.error = function(str){
| |
− | var el = $('<div></div>').addClass('err').html(str.toString().trim()).click(showAllLog);
| |
− |
| |
− | this.txt.prepend(el);
| |
− | };
| |
− | | |
− | Console.prototype.logAns = function(str){
| |
− | var el = $('<div></div>').addClass('log').html('> ' + str.toString().trim()).click(showAllLog);
| |
− |
| |
− | this.txt.prepend(el);
| |
− | };
| |
− | | |
− | Console.prototype.warnAns = function(str){
| |
− | var el = $('<div></div>').addClass('warn').html('> ' + str.toString().trim()).click(showAllLog);
| |
− |
| |
− | this.txt.prepend(el);
| |
− | };
| |
− | | |
− | Console.prototype.errorAns = function(str){
| |
− | var el = $('<div></div>').addClass('err').html('> ' + str.toString().trim()).click(showAllLog);
| |
− |
| |
− | this.txt.prepend(el);
| |
− | };
| |
− | | |
− | Console.prototype.saveHist = function(txt){
| |
− | while(this.oHistory.length)
| |
− | {
| |
− | this.history.push(this.oHistory.shift());
| |
− | }
| |
− |
| |
− | this.history.push(txt);
| |
− | };
| |
− | | |
− | Console.prototype.getHist = function(){
| |
− | var tmp = this.history.pop();
| |
− | if (!tmp || tmp.length < 3)
| |
− | {
| |
− | return '';
| |
− | }
| |
− | this.oHistory.unshift(tmp);
| |
− |
| |
− | return tmp;
| |
− | };
| |
− | | |
− | Console.prototype.getHistInv = function(){
| |
− | var tmp = this.oHistory.shift();
| |
− | if (!tmp || tmp.length < 3)
| |
− | {
| |
− | return '';
| |
− | }
| |
− | this.history.push(tmp);
| |
− |
| |
− | return tmp;
| |
− | };
| |
− | | |
− | Console.prototype.execute = function(){
| |
− | var txt = this.input.val().trim();
| |
− |
| |
− | if (txt === '')
| |
− | {
| |
− | return ;
| |
− | }
| |
− |
| |
− |
| |
− | if (txt === 'getHistory')
| |
− | {
| |
− | this.input.val('');
| |
− |
| |
− | var string = '';
| |
− |
| |
− | if(this.history.length > 0){
| |
− | var historyLength = this.history.length;
| |
− |
| |
− | for(var i = 0; i < historyLength; ++i){
| |
− | console.log(this.history[i] + '---');
| |
− | if(this.history[i] != undefined)
| |
− | string += this.history[i] + '---';
| |
− | }
| |
− | string = string.slice(0,-3);
| |
− | }
| |
− |
| |
− | if(this.history.length == 0)
| |
− | string = 'History is empty';
| |
− |
| |
− | alert(string);
| |
− | | |
− | return 0;
| |
− | }
| |
− |
| |
− | this.log(txt);
| |
− |
| |
− | if(!this.player.parse(txt))
| |
− | {
| |
− | this.input.val('');
| |
− |
| |
− | this.saveHist(txt);
| |
− | }
| |
− | else
| |
− | {
| |
− | this.warnAns('Can not disassemble');
| |
− | }
| |
− | };
| |
− | </syntaxhighlight>
| |
− | | |
− | Файл '''"phen.css"'''
| |
− | <syntaxhighlight lang="css" line start="1" enclose="div">
| |
− | *{margin:0;padding:0;}
| |
− | body{background: gainsboro;}
| |
− | .point{
| |
− | position: absolute;
| |
− | z-index: 20;
| |
− | counter-increment: num;
| |
− | text-align: center;
| |
− | border: 4px solid white;
| |
− | background: dodgerblue;
| |
− | }
| |
− | .point:before{
| |
− | content: counter(num);
| |
− | color: white;
| |
− | -webkit-user-select: none;
| |
− | -khtml-user-select: none;
| |
− | -moz-user-select: none;
| |
− | -ms-user-select: none;
| |
− | -o-user-select: none;
| |
− | user-select: none;
| |
− | }
| |
− | #screen{
| |
− | position: fixed;
| |
− | z-index: 2;
| |
− | top:27px;
| |
− | counter-reset: num;
| |
− | display: block;
| |
− | overflow-x: hidden;
| |
− | width: 100%;
| |
− | height: auto;
| |
− | bottom:0;
| |
− | background: transparent;
| |
− | }
| |
− | #btns input{
| |
− | font-size: 18px;
| |
− | position: fixed;
| |
− | z-index: 2;
| |
− | overflow: hidden;
| |
− | width: 25%;
| |
− | white-space: nowrap;
| |
− | text-overflow: ellipsis;
| |
− | color: white;
| |
− | border: 3px solid white;
| |
− | border-right: none;
| |
− | border-radius: none!important;
| |
− | outline: none!important;
| |
− | background: dodgerblue;
| |
− | }
| |
− | #btns input.hide{display:none;}
| |
− | #btns input:nth-child(2){left:25%;}
| |
− | #btns input:nth-child(3){left:50%;}
| |
− | #btns input:nth-child(4){left:50%;}
| |
− | #btns input:last-child{
| |
− | left: 75%;
| |
− | border: 3px solid white;
| |
− | }
| |
− | #btns input.notActive{}
| |
− | #btns input:active,
| |
− | #btns input.active{
| |
− | background: crimson!important;
| |
− | }
| |
− | | |
− | #btns input[disabled],
| |
− | #btns input[disabled]:active{
| |
− | background: gray!important;
| |
− | }
| |
− | .wall{
| |
− | left: 252.5px;
| |
− | top: 227.5px;
| |
− | height:0px;
| |
− | width:500px;
| |
− | z-index:1000;
| |
− | margin-top:-2px;
| |
− | position: absolute;
| |
− | -webkit-transform: rotate(180deg);
| |
− | -khtml-transform: rotate(180deg);
| |
− | -moz-transform: rotate(180deg);
| |
− | -ms-transform: rotate(180deg);
| |
− | -o-transform: rotate(180deg);
| |
− | transform: rotate(180deg);
| |
− | }
| |
− | .stick,
| |
− | .spring{
| |
− | background: white;
| |
− | width: 2px;
| |
− | position:absolute;
| |
− | -webkit-transform-origin: 50% 0;
| |
− | -khtml-transform-origin: 50% 0;
| |
− | -moz-transform-origin: 50% 0;
| |
− | -ms-transform-origin: 50% 0;
| |
− | -o-transform-origin: 50% 0;
| |
− | transform-origin: 50% 0;
| |
− | }
| |
− | .spring{
| |
− | background: url('./spring.png') no-repeat;
| |
− | background-size: 10px 100%;
| |
− | width:10px;margin-left:-5px;
| |
− | }
| |
− | input[disabled],
| |
− | input[disabled]:active{
| |
− | background: gray!important;
| |
− | }
| |
− | #stack{
| |
− | width: 130px;
| |
− | height: 260px;
| |
− | border: solid 1px black;
| |
− | border-top: 0px;
| |
− | }
| |
− | | |
− | #stack .brick{
| |
− | width: 11px;
| |
− | height: 11px;
| |
− | border: solid 1px white;
| |
− | background: white;
| |
− | float: left;
| |
− | }
| |
− | #stack .brick.on{
| |
− | background: black;
| |
− | }
| |
− | #stack .brick.now{
| |
− | background: green;
| |
− | }
| |
− | | |
− | #controls{
| |
− | position: fixed;
| |
− | z-index: 22;
| |
− | bottom: 5px;
| |
− | left: 5px;
| |
− | width: 165px;
| |
− | height: 30px;
| |
− | padding: 15px;
| |
− | -webkit-transition: all 400ms;
| |
− | -khtml-transition: all 400ms;
| |
− | -moz-transition: all 400ms;
| |
− | -o-transition: all 400ms;
| |
− | transition: all 400ms;
| |
− | opacity: .5;
| |
− | border: 3px solid #eee;
| |
− | border-radius: 20px;
| |
− | background-color: dodgerblue;
| |
− | }
| |
− | #controls.show{opacity: 1;}
| |
− | #controls:before{
| |
− | position: absolute;
| |
− | top: -2px;
| |
− | left: -2px;
| |
− | display: block;
| |
− | width: 334px;
| |
− | height: 65px;
| |
− | content: '';
| |
− | }
| |
− | #playerBtns{
| |
− | position: relative;
| |
− | left: 30px;
| |
− | }
| |
− | #playerBtns span{
| |
− | position: absolute;
| |
− | top: 4px;
| |
− | }
| |
− | #controls[type='0'] #playerBtns span:nth-of-type(1){
| |
− | top: 0;
| |
− | width: 0;
| |
− | height: 0;
| |
− | border-width: 15px 0 15px 30px;
| |
− | border-style: solid;
| |
− | border-color: transparent transparent transparent white;
| |
− | }
| |
− | #controls[type='1'] #playerBtns span:nth-of-type(1){
| |
− | top: 0;
| |
− | right: 169px!important;
| |
− | width: 11px;
| |
− | height: 30px;
| |
− | border-right: 8px solid crimson;
| |
− | border-left: 8px solid crimson;
| |
− | }
| |
− | #playerBtns span:nth-of-type(2){
| |
− | width: 0;
| |
− | height: 0;
| |
− | border-width: 10px 0 10px 20px;
| |
− | border-style: solid;
| |
− | border-color: transparent transparent transparent white;
| |
− | }
| |
− | #playerBtns span:nth-of-type(3){
| |
− | width: 0;
| |
− | height: 0;
| |
− | border-width: 10px 0 10px 20px;
| |
− | border-style: solid;
| |
− | border-color: transparent transparent transparent white;
| |
− | }
| |
− | #playerBtns span:nth-of-type(3):after{
| |
− | position: absolute;
| |
− | top: -10px;
| |
− | width: 0;
| |
− | height: 0;
| |
− | content: '';
| |
− | border-width: 10px 0 10px 20px;
| |
− | border-style: solid;
| |
− | border-color: transparent transparent transparent white;
| |
− | }
| |
− | #playerBtns span:nth-of-type(4){
| |
− | width: 0;
| |
− | height: 0;
| |
− | border-width: 10px 0 10px 20px;
| |
− | border-style: solid;
| |
− | border-color: transparent transparent transparent white;
| |
− | }
| |
− | #playerBtns span:nth-of-type(4):after{
| |
− | position: absolute;
| |
− | top: -10px;
| |
− | width: 0;
| |
− | height: 0;
| |
− | content: '';
| |
− | border-width: 10px 2px;
| |
− | border-style: solid;
| |
− | border-color: white;
| |
− | }
| |
− | #playerBtns span:nth-of-type(1){right: 165px;}
| |
− | #playerBtns span:nth-of-type(2){right: 130px;}
| |
− | #playerBtns span:nth-of-type(3){right: 90px;}
| |
− | #playerBtns span:nth-of-type(4){right: 35px;}
| |
− | #consoleWindow{
| |
− | position: fixed;
| |
− | top: 30px;
| |
− | z-index: 2;
| |
− | right: 0;
| |
− | width: 25%;
| |
− | font-weight: bold;
| |
− | font-size: 14px;
| |
− | overflow: hidden;
| |
− | max-height: 339px;
| |
− | background: white;
| |
− | }
| |
− | #consoleWindow.hide{height:0;}
| |
− | #consoleWindow.show{height: auto;}
| |
− | #consoleInput{border-bottom: 3px solid gainsboro;}
| |
− | #consoleInput:before{
| |
− | content: '>';
| |
− | font-size: 14px;
| |
− | position: absolute;
| |
− | top: 4px;
| |
− | left: 4px;
| |
− | }
| |
− | #consoleInput [type="text"]{
| |
− | border: none!important;
| |
− | outline: none!important;
| |
− | height: 20px;
| |
− | font-size: 14px;
| |
− | width: 100%;
| |
− | box-sizing:border-box;
| |
− | padding-left: 15px;
| |
− | }
| |
− | #consoleInput [type="button"]{
| |
− | font-size: 18px;
| |
− | overflow: hidden;
| |
− | width: 100%;
| |
− | right: 0;
| |
− | white-space: nowrap;
| |
− | text-overflow: ellipsis;
| |
− | color: white;
| |
− | border: 3px solid white;
| |
− | border-radius: none!important;
| |
− | outline: none!important;
| |
− | background: dodgerblue;
| |
− | }
| |
− | #consoleInput [type="button"]:active{background: crimson!important;}
| |
− | #consoleLog{
| |
− | width: 100%;
| |
− | max-height: 290px;
| |
− | box-sizing: border-box;
| |
− | height: auto;
| |
− | word-wrap: break-word;
| |
− | overflow-y: auto;
| |
− | overflow-x: hidden;
| |
− | background: white;
| |
− | padding-left: 3px;
| |
− | padding-bottom: 10px;
| |
− | }
| |
− | | |
− | #consoleLog div:not(.all){
| |
− | text-overflow: ellipsis;
| |
− | overflow: hidden;
| |
− | max-height: 37px;
| |
− | width: 100%;
| |
− | white-space: nowrap;
| |
− | }
| |
− | | |
− | #consoleLog .warn{color: #DAA520;}
| |
− | #consoleLog .err{color: crimson;}
| |
− | #consoleLog .log{color: grey;}
| |
− | </syntaxhighlight>
| |
− | | |
− | Файл '''"index.php"'''
| |
− | <syntaxhighlight lang="html5" line start="1" enclose="div">
| |
− | <!DOCTYPE html>
| |
− | <html lang="ru">
| |
− | <head>
| |
− | <title>Phen v2.0 a</title>
| |
− | <link rel="stylesheet" href="./phen.css">
| |
− | <script type="text/javascript" src="./jquery-2.1.3.min.js"></script>
| |
− | <script type="text/javascript" src="./jquery-css-transform.js"></script>
| |
− | <script type="text/javascript" src="./point.js"></script>
| |
− | <script type="text/javascript" src="./spring.js"></script>
| |
− | <script type="text/javascript" src="./stick.js"></script>
| |
− | <script type="text/javascript" src="./func.js"></script>
| |
− | <script type="text/javascript" src="./renderer.js"></script>
| |
− | <script type="text/javascript" src="./wall.js"></script>
| |
− | <script type="text/javascript" src="./phen.js"></script>
| |
− | <script type="text/javascript" src="./parser.js"></script>
| |
− | <script type="text/javascript" src="./console.js"></script>
| |
− | <script type="text/javascript" src="./player.js"></script>
| |
− | <script type="text/javascript" src="./cycle.js"></script>
| |
− | <script type="text/javascript" src="./clean.js"></script>
| |
− | </head>
| |
− | <body>
| |
− | <div id="screen"></div>
| |
− | <div id="btns">
| |
− | <input type="button" value="Open/Save" disabled="disabled">
| |
− | <input id="getPoint" type="button" value="Get point's id">
| |
− | <input id="movePoint" type="button" value="Move point">
| |
− | <input id="demovePoint" type="button" class="active hide" value="Move point">
| |
− | <input id="consoleOpen" class="active" type="button" value="Console">
| |
− | </div>
| |
− | <div id="consoleWindow" class="show">
| |
− | <div id="consoleInput">
| |
− | <input id="console" type="text" autofocus="autofocus">
| |
− | <input id="consoleBtn" type="button" value="Enter">
| |
− | </div>
| |
− | <div id="consoleLog">
| |
− | </div>
| |
− | </div>
| |
− | <div id="controls" class="show" type="0">
| |
− | <div id="playerBtns">
| |
− | <span id="stop"></span>
| |
− | <span id="stepMin"></span>
| |
− | <span id="stepMid"></span>
| |
− | <span id="stepMax"></span>
| |
− | </div>
| |
− | </div>
| |
− | </body>
| |
− | </html>
| |
− | </syntaxhighlight>
| |
− | </div>
| |
− | | |
− | [[ Медиа : MultiparticleSimulator.zip|Исходный код [php js css].zip]]
| |
− | | |
− | Суммарно код текущей версии ('''v2.0 b''' от 2 июня 2015 года) движка занимает 2 500 строк без учёта библиотеки.
| |
− | | |
− | Отличие от семейства версий '''1.*''' в полностью переписанной логике проекта для оптимизации вычислений и соблюдения принципов OOP JS.
| |
− | | |
− | <div class="mw-collapsible mw-collapsed" style="width:100%" >
| |
− | '''Разбор кода файла point.js'''
| |
− | <div class="mw-collapsible-content">
| |
− | Файл '''"point.js"'''
| |
− | <syntaxhighlight lang="javascript" line start="1" enclose="div">
| |
− | function Point(mass, radius, coorXY, oldCoorXY, isExpToPot)
| |
− | {
| |
− | this.mass = mass;
| |
− | this.invmass = 1 / mass;
| |
− | this.radius = radius;
| |
− | this.coor = coorXY;
| |
− | this.oldCoor = oldCoorXY || coorXY;
| |
− | this.id = getId();
| |
− | this.type = 'point';
| |
− | this.isExpToPot = isExpToPot || false;
| |
− |
| |
− | // log
| |
− | console.log('Create: '+this.toString());
| |
− | }
| |
− | | |
− | | |
− | Point.prototype.checkCoor = function(){
| |
− | if (this.coor.isNaN())
| |
− | {
| |
− | console.error('Coor is NaN: ' + this);
| |
− | player.stop();
| |
− | }
| |
− | else
| |
− | {
| |
− | if (this.oldCoor.isNaN())
| |
− | {
| |
− | console.warning('OldCoor is NaN: ' + this);
| |
− | this.oldCoor = new Coor(this.coor);
| |
− | }
| |
− | else
| |
− | {
| |
− | return ;
| |
− | }
| |
− | }
| |
− | }
| |
− | | |
− | Point.prototype.toString = function(type){
| |
− | if (type === 'full')
| |
− | {
| |
− | return JSON.stringify(this, [
| |
− | 'type',
| |
− | 'id',
| |
− | 'coor',
| |
− | 'mass_',
| |
− | 'radius_',
| |
− | 'x',
| |
− | 'y',
| |
− | 'begin',
| |
− | 'end'
| |
− | ], 4)
| |
− | }
| |
− | else
| |
− | {
| |
− | return JSON.stringify(this, [
| |
− | 'type',
| |
− | 'id',
| |
− | 'coor',
| |
− | 'x',
| |
− | 'y',
| |
− | 'begin',
| |
− | 'end'
| |
− | ], 4)
| |
− | }
| |
− | }
| |
− | | |
− | Point.prototype.move = function(coorXY){
| |
− | var delta = minusCoor(this.coor, this.oldCoor);
| |
− | this.coor = coorXY;
| |
− | this.oldCoor = minusCoor(coorXY, delta);
| |
− | // log
| |
− | console.log('Move: '+this.toString());
| |
− | }
| |
− | | |
− | Point.prototype.moveEase = function(coorXY){
| |
− | this.coor = coorXY;
| |
− | this.oldCoor = coorXY;
| |
− | // log
| |
− | console.log('Move: '+this.toString());
| |
− | }
| |
− | </syntaxhighlight>
| |
− | | |
− | | |
− | <syntaxhighlight lang="javascript" line start="3" enclose="div">this.mass = mass;</syntaxhighlight>
| |
− | | |
− | Сообщаем нашей частице массу, равную mass.
| |
− | | |
− | | |
− | <syntaxhighlight lang="javascript" line start="4" enclose="div">this.invmass = 1 / mass;</syntaxhighlight>
| |
− | | |
− | Вводим обратную массу, равную 1/mass. В дальнейшем работаем именно с обратным значением массы, так как это позволит вводить “бесконечно тяжёлые” частицы (значение invmass можно задать отдельно впоследствие).
| |
− | | |
− | | |
− | <syntaxhighlight lang="javascript" line start="5" enclose="div">this.radius = radius;</syntaxhighlight>
| |
− | | |
− | Задаём радиус, равный radius. Измеряется в пикселях.
| |
− | | |
− | | |
− | <syntaxhighlight lang="javascript" line start="6" enclose="div">this.coor = coorXY;</syntaxhighlight>
| |
− | | |
− | Задаём текущие координаты значениями coorXY.x и coorXY.y.
| |
− | | |
− | | |
− | <syntaxhighlight lang="javascript" line start="7" enclose="div">this.oldCoor = oldCoorXY || coorXY;</syntaxhighlight>
| |
− | | |
− | Если были заданы предыдущие координаты (oldCoor.x и oldCoor.y), то записываем их. Иначе записываем текущие координаты, тогда частица будет обладать нулевым вектором скорости
| |
− | | |
− | | |
− | <syntaxhighlight lang="javascript" line start="8" enclose="div">this.id = getId();</syntaxhighlight>
| |
− | | |
− | Генерируем уникальный номер для частицы.
| |
− | | |
− | | |
− | <syntaxhighlight lang="javascript" line start="9" enclose="div">this.type = 'point';</syntaxhighlight>
| |
− | | |
− | Указываем тип элемента как “point”, чтобы обработчик отличал частицу от других объектов.
| |
− | | |
− | | |
− | <syntaxhighlight lang="javascript" line start="10" enclose="div">this.isExpToPot = isExpToPot || false;</syntaxhighlight>
| |
− | | |
− | Сообщаем, должна ли участвовать в потенциальном парном взаимодействии. Да - если isExpToPot равняется true, нет - во всех прочих случаях.
| |
− | | |
− | | |
− | <syntaxhighlight lang="javascript" line start="13" enclose="div">console.log('Create: ‘+this.toString());</syntaxhighlight>
| |
− | | |
− | Выводим в консоль подробную информацию о созданной частице.
| |
− | | |
− | | |
− | <syntaxhighlight lang="javascript" line start="17" enclose="div">Point.prototype.checkCoor</syntaxhighlight>
| |
− | | |
− | Метод проверки значений координат положений частицы. Если текущее положение частицы не может быть определено (не верный запрос, статически неопределимая система и пр.), останавливаем симуляцию и выводим в консоль ошибку. Если не удаётся определить значения координат предыдущего положения (симуляция запущена пользователем после получения ошибки в текущих координатах частицы и пр.), выдаём предупреждение, используем координаты текущего положения также и как координаты предыдущего.
| |
− | | |
− | | |
− | <syntaxhighlight lang="javascript" line start="37" enclose="div">Point.prototype.toString</syntaxhighlight>
| |
− | | |
− | Метод, собирающий информацию о частице и выдающий её в удобном виде.
| |
− | | |
− | | |
− | <syntaxhighlight lang="javascript" line start="66" enclose="div">Point.prototype.move</syntaxhighlight>
| |
− | | |
− | Метод перемещения частицы в указанные координаты. Вектор скорости сохраняется, в консоль записывается информация о перемещении.
| |
− | | |
− | | |
− | <syntaxhighlight lang="javascript" line start="74" enclose="div">Point.prototype.moveEase</syntaxhighlight>
| |
− | | |
− | Другой метод перемещения частицы в указанные координаты. Вектор скорости зануляется, в консоль записывается информация о перемещении.
| |
− | </div>
| |
− | | |
− | == Сопроводительная информация ==
| |
− | {| class="wikitable" style="text-align:center; width: 100%;"
| |
− | |-
| |
− | ! Общая презентация <br>симулятора
| |
− | ! Подробная презентация <br> физического движка <br>в составе симулятора
| |
− | ! Отчёт
| |
− | |-
| |
− | | [[File:MultiparticleSimulator.pdf]]
| |
− | | [[File:PhysicsEngine.pdf]]
| |
− | | [[File:MultiparticleSimulatorReport.pdf]]
| |
− | |-
| |
− | | 12 слайдов
| |
− | | 55 слайдов
| |
− | | 21 страница
| |
− | |}
| |
| | | |
| == Ссылки по теме == | | == Ссылки по теме == |
− | T. Jakobsen. "Advanced Character Physics", 2003. ([http://dkhramov.dp.ua/uploads/Comp/Jakobsen/jakobsen.pdf перевод статьи <nowiki></nowiki>])
| |
− |
| |
− | Л. Ландау, Е. Лифшиц. "Теоретическая физика", том первый, "Механика", 1988.
| |
| | | |
− | А. Смирнов. "Курсовой проект: молекула углекислого газа", 2015. ([[КП: Молекула углекислого газа|страница проекта]])
| |
| | | |
| == См. также == | | == См. также == |
Строка 1667: |
Строка 39: |
| * [[Курсовые проекты ТМ]] | | * [[Курсовые проекты ТМ]] |
| | | |
− | == Примечания ==
| |
− | {{примечания}}
| |
| | | |
| [[Category: Студенческие проекты]] | | [[Category: Студенческие проекты]] |