Виртуальная модель предметной области дисциплины 'Представление знаний в информационных системах'

  • Вид работы:
    Дипломная (ВКР)
  • Предмет:
    Информационное обеспечение, программирование
  • Язык:
    Русский
    ,
    Формат файла:
    MS Word
    605,72 Кб
  • Опубликовано:
    2017-08-12
Вы можете узнать стоимость помощи в написании студенческой работы.
Помощь в написании работы, которую точно примут!

Виртуальная модель предметной области дисциплины 'Представление знаний в информационных системах'

СОДЕРЖАНИЕ

Введение

. Анализ предметной области

.1 Индустрия компьютерных игр

.2 Анализ, сравнение и выбор жанра обучающей игры

.3 Анализ существующих программных средств в области компьютерных образовательных систем

. Постановка задачи

.1 Исследование и разработка графика кривой сложности

.2 Актуальность разработки

.3 Обоснование использования виртуальной модели

.4 Обзор средств для разработки функциональных модулей

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

.1 Сюжет

.2 Модель базы знаний

.3 Анализ и выбор среды разработки игрового приложения виртуальной модели

.3.1 Corona SDK

.3.2 Unreal Engine

.3.3 Game Maker

.3.4 Construct 2

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

.1 Алгоритм динамической настройки сложности

.2 Алгоритм реакции окружения виртуальной среды на успешность прохождения игры пользователем

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

. Разработка и реализация программных модулей

.1 Описание программных модулей на языке JavaScript

.2 Отладка и экспериментальное тестирование модулей

Заключение

Список использованных источников

Приложение

ВВЕДЕНИЕ

виртуальная модель информационный алгоритм

После 31 декабря 2010 года в России в соответствии с новыми Федеральными государственными образовательными стандартами двухуровневая система высшего образования «бакалавриат (4 года) - магистратура (2 года)» стали основными для выпускников ВУЗов, что соответствует принципам Болонской системы, распространенной в Европе и Северной Америке.

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

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

Эти задачи могут быть решены при применении игровых форм обучения, так как игры позволяют эффективно организовать творческое взаимодействие преподавателя и студентов, создают условия для формирования личностных качеств.

В своё время на способностях игры решать задачи обучения и воспитания акцентировали внимание классики зарубежной и отечественной педагогики (А. Дистервег, Ф. Фребель, П.Ф. Каптерев, Н.К. Крупская, А.С. Макаренко, К.Д. Ушинский, С.Т. Шацкий, В.А.Сухомлинский) [1].

Именно поэтому целью нашей работы является обучающая игра, призванная улучшить показатели успеваемости и вовлечённости в предмет студентов.

Разрабатываемая нами игра по сути представляет собой ряд сцен, объединённых общим сюжетом и игровой механикой. В каждой сцене игроку предстоит решить задачу, основанную на информации курса “Представление знаний в информационных системах”, чтобы продвинуться дальше.

Баланс между информативной частью игры и её увлекательностью - это важный параметр любой обучающей игры. В данном случае он достигается за счёт 3 взаимосвязанных компонентов: продуманного сюжета, разнообразного игрового процесса и многоуровневой генерацией.

Используя все 3 компонента, нам удаётся поддерживать для игрока-студента баланс между увлечённостью, информативностью и сложностью материала. Это осуществляется благодаря инновационным методам расчёта сложности заданий таким образом, чтобы игра могла предоставить как увлекательность, так и информативность любому студенту, независимо от уровня его знаний. Адаптация игры под уровень пользователя происходит автоматически и не требует вмешательства администратора.

Игровой метод был протестирован на небольшой целевой аудитории студентов, и будет масштабирован на студентов курса «Представление знаний в информационных системах».

С докладом по теме «Компьютерная игра как активный метод получения знания в системе прикладного бакалавриата» прошло выступление в рамках молодежного научного форума «Молодые исследователи - регионам» в апреле 2017 года в г. Вологда. В рамках форума, организованного при участии Вологодского государственного университета, проводилось несколько мероприятий, одним из которых была международная научная конференция «Молодые исследователи - регионам». По итогам выступления тезисы были рекомендованы к публикации в сборнике материалов международной научной конференции «Молодые исследователи - регионам».

Выпускная квалификационная работа состоит из шести разделов.

В первом разделе работы производится анализ предметной области. Разбирается понятие игр в целом, эволюция игр. Производится анализ различных жанров и существующих аналогов. Происходит выбор жанра разрабатываемой обучающей игры.

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

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

В четвёртом разделе описаны самые важные алгоритмы для придания гибкости обучающей игре.

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

В шестом разделе описаны программные модули и проведено тестирование приложения.

1. АНАЛИЗ ПРЕДМЕТНОЙ ОБЛАСТИ

Исследователи ИИ зачастую используют теории представления знаний из науки «когнитологии». В ней присутствуют такие методы, как фреймы, правила вывода и семантические сети. Все они пришли в ИИ из обработки информации человеком. Поскольку знание используется для достижения разумного поведения, фундаментальной целью дисциплины представления знаний является поиск таких способов представления, которые делают возможным процесс логического вывода, то есть создание знания из знаний. [2]

Важно ответить на следующие вопросы при разработке виртуальной модели по конкретной предметной области - «Представление знаний в информационных системах»:

какие знания входят в описательную область дисциплины;

как люди видят и представляют входящие в описательную область дисциплины знания;

какова природа знаний по дисциплине.

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

знания виртуальной модели и как люди их представляют;

какова природа знаний, которые представлены в виртуальной модели;

схема представления, как она должна быть: связана ли она с частной областью знаний или общецелевая;

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

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

Уже существует довольно много обсуждений насущных, описанных выше, вопросов представления знаний и исследований в этой области. Так же обозначены хорошо известные и изученные проблемы, такие как «задача навигации в сети узлов», «категоризация» и «классификация». [3]

Однако, решать сложные задачи можно и упрощённо. Используя при этом правильный выбор метода представления знаний. Каждый метод может сделать какую-либо область знаний очень легко представимой. Например, диагностическая экспертная система MYCIN [4] использовала схему представления знаний, которая была основа на правилах. Неправильный же выбор метода довольно сильно затрудняет обработку знаний и информации.

 

.1 Индустрия компьютерных игр


Индустрия компьютерных игры (интерактивных развлечений) - это сектор экономики, который тесно связан с разработкой и продажей, а также продвижением компьютерных игр. Туда входит большое количество различных специальностей. [5]

Индустрия игр начала свою историю с 1970-х годов как новаторское движение энтузиастов, и за пару десятилетий выросла из маленького рынка в полноценное производство с годовой прибылью в 74,2 миллиарда долларов - по данным исследовательской компании SuperData за 2015 год. [6]

На рынке присутствуют и крупные компании (EA, Activision, Origin Systems, Capcom), и совсем небольшие фирмы, а также стартапы и независимы разработчики или сообщества (Kickstarter).

Современные компьютеры предоставили множество новых технологий игровой индустрии. Пожалуй, к самым значимым относят звуковые и видео карты, CD- и DVD-приводы, а также центральные процессоры.

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

Графические процессоры, которые со временем эволюционировали в направлении большего количества поддерживаемых цветов (современные видеокарты поддерживают глубину цвета до 30 бит), позже они стали развиваться для аппаратной поддержки различных графических интерфейсов пользователя (англ. GUI) и игр. Для графических интерфейсов было необходимо увеличение разрешения экрана, а для игр - ускорение не только двумерной, но и трёхмерной графики.и DVD приводы же изначально были разработаны как довольно недорогой и надежный способ хранения и передачи (или распространения) любых типов данных. Однако, позже, когда эти технологии стали использоваться в компьютерных играх, началось развитие CD и DVD приводов в сторону увеличения скорости чтения данных.

Современные игры - пожалуй, одни из самых требовательных приложений на ПК. А с появлением виртуальной и дополненной реальности, возможно, самые требовательные. Многие мощные компьютеры, приобретаемые пользователями, имеют значительные технические характеристики, которые им нужны для запуска новейших игр, в которых используются самые передовые технологии. Получается, что игровая индустрия очень тесно связана с неигровой индустрией производства ЦПУ и других компонентов ПК. Всё это потому, что игры зачастую более требовательны к аппаратным мощностям, чем обычные бизнес-приложения.

Бен Сойер (англ. Ben Sawyer) из Digitalmill изучает цепочку ценности игровой индустрии как ту, которая составлена из связанных, но хорошо различимых, шести слоев:

          уровень издательства и капитала: издатели оплачивают разработку новых проектов и прототипов и извлекают прибыль благодаря лицензированию наименований;

уровень продукта и талантов: он включает в себя таких разработчиков, дизайнеров, художников и композиторов, которые способны работать как по индивидуальным контрактам (фриланс), так и в составе большой группы разработчиков;

уровень создания и технологий: является основным источником средств разработки игр, которые настраиваются и расширяются благодаря улучшению игровых движков, связующего ПО, а также средств управления разработкой;

уровень распространения (или же индустрия издателей): являет собой создание перечней игр и их продвижение в интернете и розничных магазинах;

уровень аппаратного и программного обеспечения: сюда входят в основном аппаратные платформы, среди них есть и такие, как консоли или мобильные устройства. В этот уровень в данный момент входят ещё и неаппаратные платформы - виртуальные машины (например Flash или Java) или программные платформы, такие как браузеры и социальные сети (в последнее время), например, Facebook;

уровень конечных пользователей. Другими словами, потребителей игр или пользователей/игроков.

История всей индустрии компьютерных игр началась ещё в 1971 году с запуска аркадной игры «Computer Space». Через год компания Atari уже выпустила первую коммерчески, и не только, успешную видеоигру Pong. Успешность этой игры открыла другим аркадным автоматам дверь в "золотую эру". Путь в такие общественно значимые места как массовые торговые центры, ещё не такие популярные торговые залы, традиционные рестораны и круглосуточные магазины. Во всем мире всего было продано чуть более 360 тысяч аркадных автоматов с установленной игрой «Space Invaders», таким образом в 1982 году эта игра заработала 2 (два) миллиарда долларов монетами по 25 (двадцать пять) центов, что составляет 4,6 миллиарда долларов в современной валюте.

К началу 1980-х "золотая эра аркадных игр" была ещё далека от завершения. Объем рынка автоматов с аркадами в США довольно значительно увеличился с $50 млн. в 1978 до $900 млн. в 1981, при этом, собственно, доход всей индустрии аркадных игр утроился и стал составлять $2,8 млрд. в 1980. Самое начало этого периода ещё и совпало с появлением многих домашних компьютеров и первых энтузиастов-разработчиков игр для этих компьютеров. Самого большого успеха, на мой взгляд, достигла домашняя игровая приставка «Nintendo Entertainment System», это привело к практически тотальному захвату игрового рынка различными японскими компаниями типа «Nintendo».

В 1990-х году случилось дальнейшее развитие технологий, которые сопутствовали компьютерным играм. Я привёл наиболее значимые:

масштабное внедрение CD-ROM для хранения и данных;

масштабное и повсеместное распространение операционных систем, которые основываются на GUI, таких как Microsoft Windows, AmigaOS и Mac OS;

глобальное развитие технологий не только двумерной, но и трехмерной графики и масштабное распространение 3D видео процессоров, переход к трехмерной графике как к стандарту по умолчанию для визуализации игр;

продолжение интенсивного улучшения быстродействия CPU, всестороннее архитектурное развитие;

миниатюризация аппаратного обеспечения и его массовое распространение, увеличение числа мобильных телефонов, что приводит к появлению мобильных игровых приложений;

появление и повсеместное распространение интернета, в результате чего уже во второй половине десятилетия стала доступной совместная (многопользовательская) игра, это в свою очередь привело к появлению киберспорта.

Одновременно с развитием вышеописанных технологий происходило и развитие игрового рынка. В самом начале периода стали более успешными лицензированные игры и сиквелы игр.

В 1993 году продажи компьютерных игр в мире уже составляли $19,8 млрд. ($31 млрд. в современных ценах), $20,8 млрд. в 1994 ($32 млрд. в современных ценах) и приблизительно $30 млрд. в 1998 ($41,5 млрд. в современных ценах). Суммарные продажи внутри игровой индустрии США увеличились настолько, что теперь стали более чем в два с половиной раза выше доходов от продажи кинематографа в США.

В 2000-е было создано и стали популярными множество очень упрощённых (казуальных) и любительских инди-игр. Также крепнет направление игр для различных мобильных платформ, в том числе появилось направление создания игр для социальных сетей. Среди последних особенно известен разработчик «Zynga», делающий игры для социальной сети Facebook [7]. Другим примером успешных платформ для компьютерных игр являются iOS и Android. Здесь не могу не отметить успех вологодской компании «Playrix», которая на 2017 год входит в десятку лучших игровых компаний разработчиков мобильных игр.

На начальном этапе зарождения компьютерных игр стоимость разработки была минимальной, именно поэтому это был прибыльный бизнес. Игры, разработанные одним единственным программистом или же небольшой группой, которая обычно состояла из программиста и нескольких художников, могли обеспечивать продажи в течении продолжительного времени в количестве сотен тысяч копий. Многие из этих игр были и вовсе разработаны всего за несколько месяцев, что давало возможность тогдашним разработчикам выпускать по несколько игр в год. Это в свою очередь давало возможность издателям предлагать довольно щедрые отчисления разработчикам, включая различные бонусы с проданных копий. В течение всего этого экономически очень благоприятного периода было создано очень много известных компаний-издателей, которые и до сих пор на слуху. Например, это Origin Systems, Capcom, Activision, Sierra Entertainment, и Electronic Arts.

В настоящее же время компьютерные игр, на мой взгляд, вносят значительный вклад в мировую экономику благодаря большому успеху продаж основных игровых систем и таких игр, как, например, «Grand Thief Auto 5», собравшая в общем итоге за 3 года приблизительно $4 млрд. [8], что стало значимым рекордом продаж среди фильмов, книг и компьютерных игр.

Современным альтернативным способом издания игр может являться самостоятельное издание посредством shareware-модели, либо по модели открытого кода через интернет.

В последние годы стала набирать популярность модель "пожертвований" (осуществляющаяся с помощью сервисов аналогичных Kickstarter) для разработки компьютерных игр. Довольно часто с помощью этого метода известные в прошлом разработчики популярных игр предлагают создать идейное "продолжение" или "аналог" своих игр, если пользователи соберут запрашиваемую сумму.

 

.2 Анализ, сравнение и выбор жанра обучающей игры


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

Обучающая игра (образовательная игра) - программное обеспечение, тренирующее и обучающее человека в игровом режиме. Может применяться как для обучения, так и для развлечения. В категорию обучающая игра входят жанры - квест, аркада, 3D-шутер, симулятор, компьютерный тренажер, интерактивный курс по какому-либо предмету [9]. Программа делит на части учебный материал, и регулирует последовательность его изучения. Усвоение материала проверяется тестом, предлагаемым в конце каждого этапа обучения. [10]

Квест [9]. Это особый жанр игры, в котором должны присутствовать сюжет и задание. Сюжет может быть, как сквозной - проходящий через всю игру, так и локальный - начинающийся и завершающийся в пределах одной сюжетной кампании, сцены или группы сцен. Сюжет в квесте играет одну из самых важных ролей. Он создан для того, чтобы удерживать игрока, выдавая ему информацию о мире или каких-то событиях этого мира небольшими порциями. Второй важной частью квеста являются задания. Задания отвечают за микро прогресс игрока, позволяя ему ощущать продвижение по игре. Задания бывают двух типов: основные - это те задания, которые отвечают за продвижение сюжета, и побочные - цель этих заданий занять игрока, показать ему новые уникальные особенности игры, дать возможность отвлечься от основных заданий, если те наскучат. Лучшие квестовые игры имеют довольно высокий уровень прорисованности окружения и персонажей (рисунок 1.1).

Рисунок 1.1 - Детективная игра-квест «Ненси Дрю»

Аркада [9]. Это жанр игры, которому свойственны короткая продолжительность игровой сессии и интенсивный игровой процесс. Аркадные игры характеризуются по следующим критериям: бесконечная игра (это означает, что аркаду невозможно пройти, потенциально игроки могут играть в аркаду бесконечное время, и соответственно не могут выиграть. Основной стимул играть - это бросать вызов себе или своим друзьям, соревноваться с ними), игровой счёт (вытекает из предыдущего пункта. Игровой счёт - это количество очков, полученные игроком за выполнение различных целей и задач), простой игровой процесс (аркады нацелены на максимально широкую аудиторию, поэтому правила игры и обучение должны быть максимально простыми и понятными), отсутствие сюжета/истории (из-за бесконечности и массовости ещё не была придумана ни одна история, которая бы подавалась через аркадную игру). По всем перечисленным выше критериям очевидно, что данный жанр плохо подходит для игры, целью которой является закрепление материала и получение зачёта. Отличным примером аркады является игра «Танчики» на приставке Dendi (рисунок 1.2).

Рисунок 1.2 - Аркадная игра «Танчики»

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

Интерактивный курс или интерактивная игра. Это разновидность компьютерных игр, в которых общение между самой игрой и игроком происходит с помощью внутриигровой текстовой информации. Чаще всего игровой процесс изображается в виде меню, в котором игрок выбирает действие из нескольких предложенных. В самом широком понимании термина под понятие «Интерактивная игра» подходит любая разновидность игр, сюжет которых не является жёстко фиксированным, но способен изменяться в зависимости от действий пользователя. Очень часто именно обучающие игры являются интерактивными (рисунок 1.3).

Рисунок 1.3 - Пример интерактивной обучающей игры

Но всё же не любой жанр игры может подойти для конкретных целей обучающей игры по предметной области «Представление знаний в информационных системах». Для выбора жанра было проведено сравнение таких параметров, как простота подачи ВУЗовского материала через игру, простота усвоения данного материала, последовательность и линейность игрового процесса, отсутствия большого количества отвлекающих факторов, и, конечно, увлекательность жанра.

Изначально было выбран квест. В контексте обучающей игры по дисциплине «Представление знаний в информационных системах» была продумана концепция игры-квеста, имеющая сквозной сюжет, а также вплетающийся в основной и усиливающий его побочный сюжет, и ещё имеющая необходимое количество заданий в соотношении основных к побочным как 70/30% всего игрового времени. Однако, при реализации концепции возникла проблема неиграбельности, что было связано с нехваткой графики. Другими словами, получилось очень много текста, но очень мало картинок. Текстовый же квест не отвечает нашим основным требованиям по увлечению студента в мир игры и мотивирование его продвигаться по ней, изучая и/или закрепляя новый материал. То есть был нарушен баланс между интересностью и информативностью приложения.

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

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

 

.3 Анализ существующих программных средств в области компьютерных образовательных систем


Так как тема виртуализации обучения не нова, то и разработки в этом направлении ведутся достаточно интенсивно. К примеру, в нашем вузе уже есть программа «Инструментальная интеллектуальная программная система для генерации компьютерных тестов» (рисунок 1.4).

Рисунок 1.4 - Основной интерфейс «ИИПС 7.3»

Современные обучающие программы выглядят одинаково. Различные тесты и методические работы, даже многие виртуальные лабораторные работы выглядят не всегда интересно и не возбуждают воображение пользователей, не пробуждают в них искреннюю заинтересованность Разработчиков можно понять: главное - это функционал и адаптивность, а всё остальное отодвигается на второй план. А зря.

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

Именно это и делают воспитатели и учителя в школах с маленькими детьми [11]. Принято считать, что студент в высшем учебном заведении более собран, самостоятелен и серьёзен. Но это не всегда так, поэтому большая часть всех обучающих программ воспринимаются студентами если не с негативом, то со слабой долей заинтересованности.

На мобильном рынке обучающих приложений существует огромный спектр различных продуктов и сервисов. Пожалуй, одна из самых популярных программ для изучения иностранного языка - DuoLingo [12]. Интерфейс программы можно посмотреть на рисунке 1.5.

Рисунок 1.5 - Основной интерфейс «DuoLingo»

Это приложение обладает рядом важных преимуществ - оно не усложнено различными текстовыми объяснениями. Весь обучающий материал подаётся в лёгкой и ненавязчивой форме вопрос-ответ, где надо выбрать правильный ответ из списка доступных или же подставить нужное слово, или картинку.

Так же у приложения есть некое подобие адаптации заданий под уровень пользователя. Все темы жёстко фиксированы, но внутри темы задания составлены таким образом, чтобы интересно было их решать как и тем, у кого низкий уровень владения языком, так и желающим закрепить или повторить какую-то тему. Конечно, пользователь всё ещё зажат в строгие рамки курса и подачи материала, но уже внутри этих рамок у него есть некая возможность проявить индивидуальность.

Ещё одной замечательной игрой в обучении студентов является «Лабиринт знаний». Эта обучающая игра построена в системе «Виртуальная академия» [15] по предметной области «Представление знаний в информационных системах» изображена на рисунке 1.6.

Рисунок 1.6 - внешний вид «Лабиринта знаний»

Игра отличается своей интерактивностью. Пользователь в ней представлен в виде 3-мерного аватара, взаимодействующего с миром посредством действий пользователя. Собирая материал курса «Представление знаний в информационных системах», игрок плутал по лабиринту, и пытался найти оттуда выход. Основным минусом игры является слишком усложнённый интерфейс взаимодействия с миром. Да, игра довольно хорошая, но порог вхождения в неё был слишком высок. Именно по этой причине она так и не увидела свет в стенах родного ВУЗа.

Обучающие игры разрабатываются и в других учебных заведениях. Так, например в НИТУ «МИСиС» в 2011 году было выпущено несколько образовательных игр по различным дисциплинам. Одна из них, на мой взгляд, самая выдающаяся игра, обучающая математике - «Карлсон». Целью игры является набирание максимального количества плюшек. Игра состоит из 4-х сюжетов, в каждом из них 3 задачи в 10 вариантах. Необходимо набрать максимальное число плюшек [13]. Игра сделана полностью на Flash. И написана, и нарисована, и анимирована. Окно одного из заданий представлено на рисунке 1.7.

 

Рисунок 1.7 - внешний вид игры «Карлсон»

Подводя итог, можно заметить, что обучающих игр в любых областях довольно много. При этом все они различны как по своей цели, так и по уровню реализации. Однако, в нашем ВУЗе по дисциплине «Представление знаний в информационных системах» обучающая игра ещё не используется. Поэтому в этой узкой области аналогов данной работы нет.

2. ПОСТАНОВКА ЗАДАЧИ

Задачей данной работы является разработка обучающей игры для студентов по предметной области «Представление знаний в информационных системах». Игра должна быть интересной, познавательной, адаптирующейся под уровень знаний игрока, и быть отличным дополнением к основному материалу, выдаваемому на курсе «Представление знаний в информационных системах».

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

Программа разработана в жанре интерактивной игры с элементами квеста. Для подачи истории использованы уровни или сцены, настроены таким образом, чтобы обучить студента и/или закрепить уже полученный материал.

Игра должна адекватно реагировать на уровень знаний игрока. Давая адекватную обратную связь в виде изменяющегося окружения, и подстраивая сложность уровней под уровень студента. Благодаря этому достигается баланс между заинтересованностью, вызовом, сложностью и информативностью курса.

Каждая сценка используется как задача, которую необходимо решить студенту-игроку, чтобы продвинуться дальше по сюжету. Задачи на сценах должны быть уникальные и неповторимые. Механика, используемая на сцене для решения задачи может повторяться, но не чаще, чем через 3 сцены (простая основная механика) и не реже, чем через 5 сцен (сложная уникальная механика «Боссов»). При этом появление сложных уровней среди простых со временем должно повышаться согласно кривой сложности.

2.1 Исследование и разработка графика кривой сложности


Кривая сложности - это функция зависимости навыка игрока от времени в игре. Кривая сложности дает наглядно понять, насколько быстро игрок учится и не становится ли ему слишком скучно или сложно.

Хорошо сбалансированная по сложности игра вводит игрока в состояние потока. То есть, увлекательна и приносит удовольствие. Наша модель кривой сложности представлена на рисунке 2.1.

 

Рисунок 2.1 - график кривой сложности в нашей игре

На горизонтальной оси находится прогресс игрока (количество уровней или сцен), а на вертикальной - навык игры, необходимый для прохождения конкретного уровня. Необходимый навык игры рассчитывается в отношении удачных попыток пройти уровень к их общему числу. Другими словами - это то, с какой попытки в среднем игрок проходит уровень. К примеру, если средний пользователь проходит уровень с 10 раз, то сложность этого уровня (необходимый навык игры для его прохождения) составляет 10%. Стоит отметить, что речь идёт про среднего пользователя.

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

Рисунок 2.2 - изначальный график кривой сложности

Игра начиналась с самого простого уровня, и дальше уровень за уровнем переходила в самый трудный, в котором победить было уже практически нереально. Этот же график зачастую используется в системе обучения новому материалу. Сложность материала повышается постепенно и непрерывно.

Однако, испытав воздействие такой системе на себе и запустив небольшой тест среди друзей, мы увидели интересную картину. Практически всем игрокам в начале было скучно играть. Игра им казалось слишком простой, слишком «детской», поэтому большинство респондентов не доходили даже до её середины. С другой же стороны те, кто подходили к концу игры отмечали у себя негативные эмоции и желание выключить игру, и заняться чем-то другим. Это обусловлено высоким уровнем стресса у игрока, постоянно увеличивающегося напряжения. Когда наградой с прохождение сложного материала служил ещё более сложный.

Независимо от того, прошли игроки до конца игру или нет, все 100% респондентов отмечали дисбаланс и то, что второй раз её запускать им бы не хотелось.

Тогда мы прислушались к мнению пользователей и сделали кривую сложности с колебаниями (рисунок 2.3). То есть игра становилась сложнее, но иногда давая пользователю «передышку» - возможность отдохнуть от всё увеличивающейся сложности.

 

Рисунок 2.3 - вторая итерация графика кривой сложности

Отзывы значительно улучшились. Фактор того, что игроку часть уровней давалась просто, но при этом через какое-то время происходил вызов его способностям, положительно сказалась на вовлечённости игрока в процесс. Но казалось, что подачу материала можно было ещё улучшить. В итоге, спустя ещё несколько итераций мы пришли к нашей финальной версии кривой сложности.

Кривая сложности состоит из 3 типов уровней.

Пики на графике - это самые сложные уровни, которые крайне трудно пройти. Процент сложности этих уровней (отношение успешных попыток к их общему числу) может достигать 95. Есть несколько причин для ввода этих уровней:

вызов игроку, который важно делать время от времени, чтобы пользователь не заскучал, и чтобы у него была возможность проявить свои навыки;

замедление прогресса игрока, что важно для того, чтобы игрок не прошёл всю игру за 1 день

возможность изучить новый материал, чтобы пройти сложный уровень.

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

Впадины на графике - это лёгкие уровни. Они находятся в самом низу вертикальной оси координат. Обычно сложность этих уровней не выше 5%, что позволяет игроку передохнуть после прохождения тяжёлого уровня и ощутить «доброжелательность» игры. Такие уровни являются какого-то рода вознаграждением. Потому что их почти всегда преодолевают буквально с первой попытки, а значит увеличивается удовольствие от игры. Особенно это заметно, если до этого игрок долго не мог пройти сложный уровень, а тут сразу два или три уровня проходятся с первого раза.

Ну и третий тип уровней - это уровни средней сложности (на графике они представлены между пиками и впадинами). Их ещё можно назвать «уровни нарастания сложности». Это все те уровни, которые не относятся к очень сложным и очень лёгким. Они нужны для того, чтобы добавить разнообразие в игру, а также подвести игрока к сложным уровням, создавая некую лестницу. Без них игра бы напоминала американские горки с резкими подъёмами и спусками. Средние уровни очень важны, так как они позволяют сгладить кривую сложности и частично замаскировать её.

Как можно заметить вопросу баланса было уделено много внимания и сил. Хочется надеяться, что данные исследования легко смогут быть масштабированы на большую аудиторию, нежели контрольная группа из друзей и знакомых. Однако, даже если нет, то небольшие коррективы в график внести не составит большого труда, если понимать его смысл.

 

.2 Актуальность разработки


Тема разработки виртуальной модели «Представление знаний в информационных системах» актуальна, так как на данный момент на рынке информационных систем существует очень мало аналогов предлагаемого ресурса, а в данном курсе действующих аналогов и вовсе нет. Существующие информационные системы направлены на обучение в виде дистанционного образования, либо как курс обязательных дисциплин, входящих в учебный план специальности. Отсюда можно сделать вывод, что пользователи, которые будут проходить обучение, вряд ли найдут что-то похожее. Анализ аналогичных систем показал, что на сегодняшний день на рынке существует ограниченный выбор информационных ресурсов, позволяющих осуществлять процесс обучения.

 

.3 Обоснование использования виртуальной модели


Основные плюсы виртуальных моделей [14]:

возможность обучаться в любое время - студент, обучающийся дистанционно, может самостоятельно решать, когда и сколько времени в течение семестра ему уделять на изучение материала. Он строит для себя индивидуальный график обучения. Некоторые образовательные учреждения предоставляют своим студентам возможность откладывать обучение на длительный срок и возвращаться к нему без необходимости снова оплачивать образовательные услуги;

возможность обучаться в своем темпе - учащимся дистанционно не нужно беспокоиться о том, что они отстанут от своих однокурсников. Всегда можно вернуться к изучению более сложных вопросов, главное, успешно проходить промежуточные и итоговые аттестации;

возможность обучаться в любом месте - студенты могут учиться, не выходя из дома или офиса, находясь в любой точке мира. Чтобы приступить к обучению, необходимо иметь компьютер с доступом в Интернет. Отсутствие необходимости ежедневно посещать учебное заведение несомненный плюс для людей с ограниченными возможностями здоровья, для проживающих в труднодоступных местностях, отбывающих наказание в местах лишения свободы, родителей с маленькими детьми;

учеба без отрыва от основной деятельности - дистанционно можно обучаться на нескольких курсах одновременно, получать очередное высшее образование. Для этого совсем не обязательно брать отпуск на основном месте работы, уезжать в командировки. Существуют образовательные организации, которые организуют корпоративное обучение (повышение квалификации) для сотрудников фирм и госслужащих. В этом случае учеба не прерывает трудовой стаж, а изученные вопросы можно сразу применить в трудовой деятельности;

высокие результаты обучения - как показывают исследования американских ученых, результаты дистанционного обучения не уступают или даже превосходят результаты традиционных форм обучения. Большую часть учебного материала студент-дистанционник изучает самостоятельно. Это улучшает запоминание и понимание пройденных тем. А возможность сразу применить знания на практике, на работе помогает закрепить их. Кроме того, использование в процессе обучения новейших технологий делает его интереснее и живее;

мобильность - связь с преподавателями, репетиторами осуществляется разными способами: как on-line, так и off-line. Проконсультироваться с тьютором с помощью электронной почты иногда эффективнее и быстрее, чем назначить личную встречу при очном или заочном обучении;

доступность учебных материалов - обучающимся дистанционно незнакома такая проблема, как нехватка учебников, задачников, методичек. Доступ ко всей необходимой литературе открывается студенту после регистрации на сайте университета, либо он получает учебные материалы по почте;

дистанционное образование дешевле - если сравнивать обучение по отдельно взятой специальности на коммерческой основе очно и дистанционно, то второе окажется дешевле. Студенту не приходится оплачивать дорогу, проживание, а в случае с зарубежными вузами не нужно тратиться на визу и загранпаспорт;

обучение в спокойной обстановке - промежуточная аттестация студентов дистанционных курсов проходит в форме on-line тестов. Поэтому у учащихся меньше поводов для волнения перед встречей с преподавателями на зачетах и экзаменах. Исключается возможность субъективной оценки: на систему, проверяющую правильность ответов на вопросы теста, не повлияет успеваемость студента по другим предметам, его общественный статус и другие факторы;

удобство для преподавателя - учителя, репетиторы, преподаватели, занимающиеся педагогической деятельностью дистанционно, могут уделять внимание большему количеству учеников и работать, находясь, например, в декретном отпуске;

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

Так же современные виртуальные модели решают ряд бывших проблем и превращают их минусы в плюсы:

развитие коммуникативных способностей - прохождение всего возможно в группе благодаря использованию многопользовательского режима. Таким образом студенты заочного или дистанционного обучения, изучающие один курс, могут изучать его вместе, общаться, делиться впечатлениями, взаимодействовать друг с другом; в общем делать всё тоже самое, что они могли бы делать, если бы ходили в занятие в аудиторию;

положительное воздействие на мотивацию учащихся - это достигается с помощью использования игровой формы обучения, виртуальных наград за прохождение, системы постепенного усложнения прохождения территории.

Однако, остаётся пара пунктов, которые вызывают затруднения при использовании виртуальных моделей:

недостаток практических знаний - обучение специальностям, предполагающим большое количество практических занятий, дистанционно затруднено. Даже самые современные тренажеры не заменят будущим медикам или учителям «живой» практики;

проблема идентификации пользователя - пока самый эффективный способ проследить за тем, честно и самостоятельно ли студент сдавал экзамены или зачеты, - это видеонаблюдение, что не всегда возможно. Поэтому на итоговую аттестацию студентам приходится лично приезжать в вуз или его филиалы;

недостаточная компьютерная грамотность - в России особенная потребность в дистанционном образовании возникает в отдаленных районах. Однако в глубинке не у всех желающих учиться есть компьютер с доступом в Интернет.

В скором будущем и эти проблемы будут решены, так как прогресс идёт очень быстро и эффективно, если пользоваться его благами.

 

.4 Обзор средств для разработки функциональных модулей


Необходимо разработать виртуальную модель с удобным пользовательским интерфейсом для обучения студентов Вологодского государственного университета по предмету «Представление знаний в информационных системах». Разработка системы должна быть легко понятной и не занимать большого количества времени на изучение языка программирования.

Согласно исследованию, проведённому аналитическим порталом dev.by, язык JavaScript находится в десятке самых популярных. Исследование было проведено на основании не менее десяти предварительно исследованных источников, среди которых [16]:

поисковые выдачи Google;

данные из Google Trends;

сообщения в Twitter;

репозитории GitHub;

вопросы на StackOverflow;

посты Reddit.

В сравнении участвовали такие языки, как C, C#, Java. Однако, C не хватает объектного ориентирования. C# и Java полностью объектно-ориентированы и кроссплатформенны, но уступают JavaScript в простоте освоения.

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

Для создания виртуальной предметной области был использован язык JavaScript (или VJScript), а также вспомогательный язык Lua.

Работа с игровыми объектами виртуальной модели является основным процессом при создании приложения. Все использованные объекты были разработаны самостоятельно и созданы с помощью бесплатных программ для моделирования, таких как «Paint» и «GIMP». Созданные объекты загружаются на сервер, откуда их можно в любое время и в любом количестве вызывать и располагать в виртуальной среде. У каждого объекта есть параметры: текстура, размер, расположение. Популярность языка JavaScript связана с его широкими возможностями по взаимодействию с элементами без перезагрузки программы [17]. Это позволяет прятать и показывать фрагменты дизайна, перемещать их и менять оформление. Путем таких действий можно создавать презентационные эффекты, меню, небольшие игры, управлять содержимым.

Работа с формами виртуальной модели. Через скрипты удобно получать и обрабатывать любые данные форм, это позволяет проверить информацию на правильность ввода перед ее отправкой на сервер или иметь обратную связь пользователя с сервером без остановки выполнения скриптов.

Работа с изображениями. Через скрипты можно делать предварительную загрузку изображений. Использовать изображения в качестве текстур для 2D-объектов можно присваивать непосредственно во время выполнения сценария. Основные параметры изображений, такие как: ширина, высота картинок и адрес графического файла, тоже можно менять динамически. Это позволяет создавать эффект перекатывания, когда текстура объекта меняется при взаимодействии с ним или прекращения взаимодействия.

Отслеживание событий. Событием называется определенное действие пользователя или изменение состояния документа. JavaScript отслеживает большинство событий и позволяет определять реакцию на них. Например, при загрузке веб-страницы происходит событие onLoad. Если необходимо запустить скрипт сразу после загрузки документа, следует этому событию назначить функцию, которая будет выполняться при его наступлении.

Математические функции. JavaScript содержит все необходимые арифметические операции, поддерживает все стандартные математические функции, как с целыми числами, так и с плавающей точкой.

Язык VJavaScript предназначен для программирования объектов внутри одной локации. Язык представляет собой расширенный JavaScript и поддерживает все языковые конструкции базового JavaScript.

Язык позволяет управлять всеми пользовательскими объектами внутри одной локации, обращаясь к ним по имени. По имени объекта можно получить ссылку на него, а имея ссылку поставить обработчики на любые события.

Программа на VJavaScript не имеет смысла без объектов, которыми она управляет. Работа программы не начинается до тех пор, пока все объекты, используемые программой, не будут загружены.

Ссылка на объект получается с помощью вызова метода getObjectByName глобального объекта scene. Обычно единственный параметр этой функции является фиксировано заданной строкой. Однако, возможно формирование имени объекта на лету в строковую переменную. Работа программы не начинается до тех пор, пока все объекты, используемые программой не будут загружены.

Разумеется, здесь описаны не все возможности языка JavaScript, но и указанного достаточно, чтобы понять, что это мощное, гибкое, универсальное и удобное средство работы как с логикой сценариев, так и непосредственно с объектами.

3. РАЗРАБОТКА ВИРТУАЛЬНОЙ МОДЕЛИ ПРЕДМЕТНОЙ ОБЛАСТИ

Разработка игр - дело непростое, многогранное. Тот, кто задумал разработать игру, может подступиться к этому вопросу с совершенно разных сторон. И, на мой взгляд, начинать с технической стороны не совсем верно. Можно быть гением Unity или двухмерного движка типа phaser.js и все равно сделать неинтересную игру низкого качества. Игры - это не только техническая реализация: любая хорошая игра - это синергия звука, цвета, движения и, самого главного - сценария, истории. Именно она позволяет игрокам полноценно окунуться в мир игры, забыв обо всем на свете. В данном разрезе стоит игнорировать игры вроде «Счастливого фермера» или «Candy Crush». У них нет толковой сценарной составляющей, но они все равно ужасно затягивают, не благодаря их качеству, а потому что «играют» на умении мозга получать удовольствие от малейшего стимула. Такие игры, сочетая простоту и красочность, делают зависимыми от них. Кто-то из гейм-индустрии сказал о том, что игра, которая заставляет тебя думать о ней все время, плохая, наркотик.

С чего начать разрабатывать игру? С истории и сюжета. Любая техническая сторона вспомогательна. Если кто-то скажет, что только гигантские корпорации, разрабатывающие 3D игры и миры, выпускают качественные продукты, то они не слышали об успешных инди-играх вроде Hotline Miami 1/2, Meat Boy, Fez. Они, может быть, не блещут графикой, но зато сверкают своим глубоким миром, который приглашает миллионы людей окунуться в волшебное пространство игры.

 

.1 Сюжет


Сюжет в данной игре является основной мотивационной составляющей. Он написан по правилам написания сюжетов для мобильных игры, используемых современными игровыми компаниями. В качестве основы был взят алгоритм “Личность -> желания -> проблемы в осуществлении -> сюжет”. Персонажем игры мы выбрали студента, опоздавшего на зачёт по предмету “Представление знаний в информационных системах” и пытающегося найти преподавателя, чтобы тот ему поставил оценку. Персонаж частично обезличен: он не имеет пола, его имя не называется, а всё повествование ведётся от первого лица. Это упрощает игроку (предположительно студенту, изучающую данную дисциплину) сопереживать персонажу и начать себя с ним ассоциировать.

Итак, персонаж студент опоздал на зачёт по предмету “Представление знаний в информационных системах”. Дверь в аудиторию оказалась закрыта на кодовый замок, но код должен быть написан где-то рядом.

Перед игроком предстаёт дверь в РЦДО. Справа от двери кодовый замок. При нажатии на него, по середине экрана появляется окно, в котором предлагается ввести 4 цифры (ввод происходит с клавиатуры в строку, вводить можно любые символы). Про 4 цифры нам говорится только в подписи над строкой. Стены вокруг расписаны разными надписями и цифрами: “Люблю Полину!1111”, “А я люблю Алана”, “Тьюринга?”, “Да, прямо со дня его рождения”, “Ты в 1912 ещё даже не родилась!”. Когда игрок догадывается, что код от дверей 1912 и вводит его, то дверь открывается. Далее игрок переходит ко 2 сцене.

Во 2 сцене мы подводим игрока к понимаю основной механики игры, намекая ему на предстоящие впереди задания. Эта часть подаётся как мысли главного героя, появляющиеся на тёмном фоне. Перед собой студент видит ещё одну дверь, уже обычную, не похожую на дверь в РЦДО. Следующее задание выбирается уже из информации о прохождении 1 сцены. Либо задание будет усложнено, либо упрощено. Мы рассмотрим средний вариант: Над дверью находится надпись: «Лишь постигший все модели представления знаний сможет пройти». Словосочетание «все модели» выделено жирным курсивом и цветом. На стенах он видит надписи: продукционные, формальные логические, семантические сети, фреймы, безупречного обслуживания, закрытая, открытая, коммуникативная. В усложнённом варианте задания часть надписей скрыта и надо ввести их самостоятельно, в упрощённом - часть уже написанных правильных надписей подсвечена. При нажатии на какую-то надпись, она меняет свой цвет с белого на зелёный. При нажатии на «все модели» происходит проверка выделенных надписей. Если выделены все нужные, то дверь откроется. Если не все нужные выделены или выделены лишние, то выделение всех надписей сбрасывается и даётся вторая попытка.

В 3 сцене над дверью находится надпись: «Набор инструкций, задающих последовательность действий по преобразованию некоторой совокупности исходных данных для получения определенного результата». Справа от неё кодовый замок, куда предлагается ввести определение. В усложнённой версии задания слова в определении перепутаны. Сначала их надо поставить на свои места, только после этого разблокируется панель для ввода. В упрощённом варианте некоторые буквы внутри определения подсвечены, из них можно составить часть самого определяемого слова.

В 4 сцене задание над дверью гласит «Определение ты знаешь! Но какие свойства его раскрывают?». Словосочетание свойства выделено жирным курсивом и цветом. На стенах надписи: Дискретность, Определенность, Результативность, Массовость, Непрерывность, Неопределённость, Безрезультатность, Уникальность. Система прохождения уровня такая же, как во второй сцене.

В 5 сцене, которая является ключевой для этого ряда задач, уже предлагается ввести алгоритм для машины Тьюринга по преобразованию последовательности символов из текущей в необходимую. Сложность алгоритма определяется количеством шагов, необходимых для решения задачи. И задаётся она в зависимости от совокупности результатов прохождения всех предыдущих 4 заданий. Ввод осуществляется посредством нажатия на плиты на полу у дверей. Проверка результата и задание находится над дверью сверху.

 

.2 Модель базы знаний


База знаний, БЗ - это особого рода база данных, разработанная для управления знаниями (метаданными), то есть сбором, хранением, поиском и выдачей знаний [18]. Раздел искусственного интеллекта, изучающий базы знаний и методы работы со знаниями, называется инженерией знаний. Под базами знаний понимается совокупность фактов и правил вывода, допускающих логический вывод и осмысленную обработку информация.

Простые базы знаний могут использоваться для создания экспертных систем и хранения данных об организации: документации, руководств, статей технического обеспечения. Главная цель создания таких баз - помочь менее опытным людям найти существующее описание способа решения какой-либо проблемы предметной области.

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

Онтология может служить для представления в базе знаний иерархии понятий и их отношений. Онтология, содержащая еще и экземпляры объектов не что иное, как база знаний.

База знаний - важный компонент интеллектуальной системы. Наиболее известный класс таких программ - экспертные системы. Они предназначены для построения способа решения специализированных проблем, основываясь на записях БЗ и на пользовательском описании ситуации.

Для удобства оформления было решено использовать продукционную модель представления знаний.

Продукции (наряду с сетевыми моделями) являются наиболее популярными средствами представления знаний в информационных системах. В общем виде под продукцией понимают выражение вида A ® B. Обычное прочтение продукции выглядит так: ЕСЛИ А, ТО B. Импликация может истолковываться в обычном логическом смысле, как знак логического следования B из истинного А. Возможны и другие интерпретации продукции, например, А описывает некоторое условие, необходимое, чтобы можно было совершить действие B.

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

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

Приведем сильные и слабые стороны систем продукций.

Сильные стороны систем продукций:

модульность;

единообразие структуры (основные компоненты продукционной системы могут применяться для построения интеллектуальных систем с различной проблемной ориентацией);

естественность (вывод заключения в продукционной системе во многом аналогичен процессу рассуждения эксперта);

гибкость родовидовой иерархии понятий, которая поддерживается только как связь между правилами (изменение правила ведет за собой изменение в иерархии);

простота создания и понимания отдельных правил;

простота пополнения и модификации;

простота механизма логического вывода.

Слабые стороны систем продукций:

процесс вывода менее эффективен, чем в других системах, поскольку большая часть времени при выводе затрачивается на непроизводительную проверку применимости правил;

сложно представить родовидовую иерархию понятий;

неясность взаимных отношений правил;

сложность оценки целостного образа знаний;

отличие от человеческой структуры знаний;

отсутствие гибкости в логическом выводе.

Представление знаний с помощью продукций иногда называют «плоским», так как в продукционных системах отсутствуют средства для установления иерархий правил. Объем знаний продукционных систем растет линейно, по мере включения в нее новых фрагментов знаний, в то время как в традиционных алгоритмических системах, использующих деревья решений, зависимость между объемом база знаний и количеством знаний является логарифмической.

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

 


3.3 Анализ и выбор среды разработки игрового приложения виртуальной модели


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

 

.3.1 Corona SDK

Corona SDK - средство кроссплатформенной разработки для Android/iOS от Ansca Mobile (основатели компании в прошлом работали в Adobe над Flash Lite) [19]. Corona SDK предназначен прежде всего для разработки игр, но нем можно писать и бизнес-приложения - для этого в движке есть набор UI-компонентов со сменными стилями. Главным преимуществом движка является значительное увеличение скорости разработки для опытных программистов, а также снижение технического барьера для новичков в мобильном gamedev’е.

Плюсы Corona SDK:

разработка на простом и элегантном динамическом языке - Lua

симулятор позволяет видеть сделанные изменения моментально

простота настройки рабочего окружения (достаточно скачать симулятор и использовать редактор кода на ваш вкус, компиляция происходит на удаленном сервере)

компактный и интуитивный API

качественная документация от разработчиков движка

большое и активное сообщество разработчиков (количество бесплатной помощи действительно огромно и это очень помогает на начальных этапах + стимулирует помогать новичкам, когда сам уже что-то знаешь)

стабильность и скорость движка

быстрое исправление ошибок разработчиками - у многих это самое слабое звено (например, с Titanium Mobile мои баг-репорты висели в баг-трекере месяцами, с Сorona - от пары дней, до пары недель)

Минусы Corona SDK:

лицензия $200-350/год (все вышеперечисленные средства бесплатны)

невозможность подключать сторонние библиотеки, написанные не на Lua (скажем, модуль издателя игры или баннерной сети)

критичный к быстродействию код все равно придется писать на Lua, если нет соответвующей функции в API движка, или искать обходные пути (они, как правило, есть)

некоторые различия в функционале версий для Android и iOS (например, платежи внутри приложения поддерживаются пока только на iOS)- это программная платформа основанная на скриптовом языке Lua, предоставляющая большое количество API и плагинов, позволяющих разработчику быстро и легко создавать приложения, которые будут работать на разнообразных устройствах.

С помощью встроенного симулятора вы сможете попробовать, как ваше приложение будет смотреться на различных устройствах, и как оно будет работать, и всё это без развёртывания на реальном устройстве. Когда ваше приложение будет готово для тестирования на настоящем устройстве, вы сможете скомпилировать и установить его на целевые устройства для финальной проверки перед размещением в магазины приложений.

Дополнительно к основному функционалу Corona, вы можете использовать плагины, которые добавят специфические функции и помогут повысить скорость разработки. Например, для встраивания рекламных объявлений вы можете использовать плагин Corona Ads или один из множества других аналогичных плагинов. Плагины для монетизации, аналитики, сетевых игр, встроенных покупок, взаимодействия с социальными сетями и многие другие являются динамической частью экосистемы Corona, и новые плагины появляются на постоянной основе. С их помощью вы можете сосредоточиться на создании вашего приложения, используя стабильные дополнения.

По сути, Corona сфокусирована на том, чтобы помогать вам создавать приложения быстро. Большинство пользователей заявляют, что они могут завершить проект намного быстрее, используя Corona, по сравнению с другими системами. А учитывая дополнительное преимущество в написании кода только один раз для большинства платформ, выбор Corona напрашивается сам собой.

 

.3.2 Unreal Engine

Unreal Engine - игровой движок, разрабатываемый и поддерживаемый компанией Epic Games.

Написанный на языке C++, движок позволяет создавать игры для большинства операционных систем и платформ: Microsoft Windows, Linux, Mac OS и Mac OS X; консолей Xbox, Xbox 360, Xbox One, PlayStation 2, PlayStation 3, PlayStation 4, PSP, PS Vita, Wii, Dreamcast, GameCube и др., а также на различных портативных устройствах, например, устройствах Apple (iPad, iPhone), управляемых системой iOS и прочих. (Впервые работа с iOS была представлена в 2009 году, в 2010 году продемонстрирована работа движка на устройстве с системой webOS).

Для упрощения портирования движок использует модульную систему зависимых компонентов; поддерживает различные системы рендеринга (Direct3D, OpenGL, Pixomatic; в ранних версиях: Glide, S3, PowerVR), воспроизведения звука (EAX, OpenAL, DirectSound3D; ранее: A3D), средства голосового воспроизведения текста, распознавание речи), модули для работы с сетью и поддержки различных устройств ввода.

На данный момет функционально UE, блаодаря Blueprints (визуальное программирование), мощному редактору материалов и ИИ впереди своих конкурентов. У других движков нет возможности использовать удобное визуальное программирование, сильно сокращающее затраты времени. Новые параметры постоянно добавляются, коммьюнити создаёт собственные блюпринты, которые невозбранно можно использовать. А если этого мало, то С++ в помощь, с его помощью можно сделать всё, что угодно. Есть модуль для добавления Шарпа, что облечгает переходит Unity-разработчиков.

Несмотря на то, что Unreal Engine 4 является движком для создания компьютерных игр, его можно использовать в самых различных целях. В данной статье я хотел выставить его со стороны образования и как личного помощника в развитии своих мыслительных способностей. Не хочу судить свой текст, однако я постарался расписать максимально понятно для людей любого возраста и профессии и, надеюсь, в будущем движок поможет очень многим, в том числе юному поколению.

Конечно, не стоит рассматривать UE4 как набор кнопок «сделать все хорошо». Инструмент очень мощный с впечатляющим функционалом, поэтому изучить все и сразу тоже не получится.

 

.3.3 Game Maker

Game Maker - один из самых известных конструкторов игр. Написан на Delphi. Доступен для ОС Windows, 7-я версия программы также существовала в версии для Mac. Ведущий разработчик - Марк Овермарс.

Система рассчитана в основном на создание двухмерных (2D) игр любых жанров. Также подойдёт для создания различных презентаций и т. п. Начиная с 6-й версии появилась ограниченная возможность работать с 3D.

Может быть рекомендован для изучения программирования. Будучи профессором Утрехтского университета, Марк Овермарс начал разрабатывать Game Maker как учебное пособие для своих студентов.

Одна из вещей, которая делает Game Maker: Studio таким уникальным, так это легкость, с которой простые команды и функции могут быть сделаны, чтобы манипулировать вашим игровым миром используя кнопки действий. Эти действия приходят в виде набора действий, каждое множество, имеющее ряд иконок, которые можно перетащить в события объекта, для создания поведения этого объекта.

Язык программирования, GML, дает гораздо больше гибкости и контроля, чем стандартные действия, которые доступны через Drag'n'Drop интерфейс. Есть много различных мест, где можно использовать этот язык для создания и управления в вашей игре. Сам язык имеет синтаксис от js, php, pascal. Собственно можно писать как на одном из этих языков, что не может не радовать.

Плюсы:

кроссплатформенность;

гибкая ценовая категория и невысокие цены;

собственный язык программирования Game Maker Language (GML);

интеграция со Steam;

поддержка множества интернет-площадок «из коробки» (Developer Services Portal);

поддержка Шейдеров

интеграция с Facebook

поддержка работы с сетью (TCP/IP и Bluetooth)

Минусы:

плохо оптимизирован для больших игр (эту проблему может решить YoYo Compiler);

разработчики перестали развивать режим 3D вообще;

Так же имеется удобные и функциональные редакторы игровых ресурсов. Стоит отметить сохранившеюся частичную совместимость со старыми проектами Game Maker.

 

.3.4 Construct 2

Construct 2 позволяет каждому желающему создавать 2D-игры любой сложности и любого жанра, даже не имея навыков программирования. Игры, сделанные на нём, легко портируются на все основные платформы - PC, Mac, Linux, браузеры с поддержкой HTML5, Android, iOS, Windows Phone, Blackberry 10, Amazon Appstore, Chrome Web Store, Facebook и пр. Поддержка iOS и Android осуществляется благодаря технологиям CocoonJS от Ludei, directCanvas от appMobi и Intel XDK, которые используют аппаратное ускорение для увеличения производительности HTML5 игр в 5-10 раз [20].

Интерфейс программы интуитивно понятен и прост в освоении, благодаря визуальному WYSIWYG-редактору от человека не требуется знаний программирования и опыта в разработке игр, в ней может разобраться даже ребёнок. Логика игр в Construct 2 создается с помощью системы событий (англ. events) и связанных с ними действий (англ. actions).

Для овладения Construct 2 требуется лишь базовый опыт работы с ПК и немного усилий. Вдобавок, постоянные обновления делают использование программы достаточно безопасным.

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

На рисунке 3.1 представлен основной интерфейс программы.

 

Рисунок 3.1 - Основной интерфейс «Construct 2»

По большому счёту, Construct 2 является улучшенной версией программы Construct Classic, имеет больше возможностей, а также умеет портировать игры на огромное количество мобильных платформ и не только. Движок был создан в 2007 году и за 10 лет сильно изменился.

Да, Construct 2 является условно-бесплатным конструктором игр, однако, полная версия программы со всеми возможностями обойдётся вам всего в $119. Хотя, если вы хотите ознакомиться с основными функциями и особенностями конструктора, вам хватит бесплатной версии.

Интерфейс программы максимально прост. Присутствует два основных окна: «layout» и «event sheet». «Layout»- это ваш уровень. Здесь вы можете расставить предметы, врагов. «Event sheet»- это лист событий, всё, что будет происходить на данном уровне. Хочется заметить, что каждый «layout» закреплён к определённому «event sheet», что на практике очень удобно.

В программе также используется продукционное программирование. Присутствует «Condition»- условие и «Action»- действие, которое произойдёт, если будет выполнено условие.

Вы сможете добавить к вашим игровым объектам много поведений (Behaviors). Например, если вы делаете 2D платформер, вам не нужно прописывать код для вашего персонажа, про добавьте ему поведение «Platform». У него автоматически появится гравитация, он сможет прыгать и бегать на установленные вами клавиши.

Плюсы:

доступен каждому;

прост в использовании;

огромное количество плагинов, эффектов и поведений

постоянные обновления;

быстрое тестирование проектов;

кроссплатформенность.

Минусов за время работы замечено не было.

4. РАЗРАБОТКА АЛГОРИТМОВ ПОСТРОЕНИЯ ВИРТУАЛЬНОЙ МОДЕЛИ ПРЕДМЕТНОЙ ОБЛАСТИ

Баланс между информативной частью игры и её увлекательностью - это важный параметр любой обучающей игры. В данном случае он достигается за счёт 3 взаимосвязанных компонентов: продуманного сюжета, разнообразного игрового процесса и многоуровневой генерацией.

Про сюжет и игровой процесс уже было сказано выше. Осталась многоуровневая генерация. По мере прохождения игры, задания усложняются. Часть ключевых сцен одинаковы для всех игроков, это точки корректирования «кривой сложности» для игрока. Для не ключевых сцен используется результат алгоритма, отслеживающий прогресс игроков и то, насколько легко ему даются задачи. Этот алгоритм выбирает те задания, которые будут достаточно сложными и интересными для тех, кому прохождение обычно даётся легко, но в меру простыми и информативными для тех, кто тратит много попыток на прохождение каждой конкретной сцены.

Ещё один уровень генерации контента тоже непосредственно связан с успешностью прохождения игры игроком. Но на сей раз алгоритм в зависимости от количества неправильных ответов изменяет обстановку на уровнях, создавая иллюзию “хорошо” и “плохо”. Например, игрок, который часто ошибается с большой вероятностью увидит следующую сцену более мрачной. Редко ошибающийся же игрок будет оказываться во всё более облагороженных помещениях и двери перед ним всё больше будут походить на вход в роскошные апартаменты.

Чтобы алгоритм выполнил свое предназначение, его необходимо строить по определенным правилам. Поэтому нужно говорить все же не о свойствах алгоритма, а о правилах построения алгоритма, или о требованиях, предъявляемых к алгоритму.

Первое правило - при построении алгоритма, прежде всего, необходимо задать множество объектов, с которыми будет работать алгоритм. Формализованное (закодированное) представление этих объектов носит название данных. Алгоритм приступает к работе с некоторым набором данных, которые называются входными, и в результата своей работы выдает данные, которые называются выходными. Таким образом, алгоритм преобразует входные данные в выходные. Пока мы не имеем формализованных входных данных, мы не можем построить алгоритм.

Второе правило - для работы алгоритма требуется память. В памяти размещаются входные данные, с которыми алгоритм начинает работать, промежуточные данные и выходные данные, которые являются результатом работы алгоритма. Память является дискретной, т. е. состоящей из отдельных ячеек. Поименованная ячейка памяти носит название переменной. В теории алгоритмов размеры памяти не ограничиваются, т. е. считается, что мы можем предоставить алгоритму любой необходимый для работы объем памяти.

Третье правило - дискретность. Алгоритм строится из отдельных шагов (действий, операций, команд). Точнее - из множества шагов.

Четвертое правило - детерминированность. После каждого шага необходимо указывать, какой шаг выполняется следующим, либо давать команду остановки.

Пятое правило - сходимость (результативность). Алгоритм должен завершать работу после конечного числа шагов. При этом необходимо указать, что считать результатом работы алгоритма.

 

.1 Алгоритм динамической настройки сложности


Алгоритм динамической настройки сложности позволяет подбирать задание на каждой сцене по силам студенту. Его структура приведена на рисунке 4.1.

Рисунок 4.1 - Алгоритм подстройки сложности

Алгоритм динамической настройки сложности состоит из следующих шагов:

)        загружаются и проверяется наличие переменной Complex (сложность);

)        делаем проверку SceneNumber (номера уровня) и увеличиваем значение Complex на 1 (чтобы решить вопрос со сложностью 1 уровня);

)        далее сверяем SceneNumber и Complex между собой:

а) если если Complex < SceneNumber, то выбираем лёгкий уровень;

б) если если Complex = SceneNumber, то выбираем нормальный уровень;

б) если если Complex > SceneNumber, то выбираем сложный уровень;

)        загружаем уровень необходимой сложности;

)        проверяем, насколько легко игрок проходит уровень, замеряя кол-во попыток (потраченного времени) на уровень;

)        сохраняем значение фактической сложности в переменную Complex данного уровня;

 

.2 Алгоритм реакции окружения виртуальной среды на успешность прохождения игры пользователем


Алгоритм реакции окружения виртуальной среды на успешность прохождения игры пользователем приведён на рисунке 4.2.

Рисунок 4.2 - Алгоритм реакции окружения виртуальной среды на успешность прохождения игры пользователем

Алгоритм реакции окружения виртуальной среды на успешность прохождения игры пользователем состоит из следующих шагов

)        загружаются и проверяется наличие переменной Success (успешность прохождения игры);

)        далее сверяем Success и MainSuccess между собой:

а) если Success < MainSuccess, то выбираем «плохую» обстановку уровня;

б) если Success = MainSuccess, то выбираем «нормальную» обстановку уровня;

б) если Success > MainSuccess, то выбираем «красивую» или «хорошую» обстановку уровня;

)        загружаем уровень необходимого качества;

)        проверяем, насколько успешно игрок проходит уровень, замеряя кол-во попыток (потраченного времени) на уровень;

)        сохраняем значение фактической успешности в переменную Success;

5. РАЗРАБОТКА МОДЕЛЕЙ БАЗ ДАННЫХ ВИРТУАЛЬНОЙ ПРЕДМЕТНОЙ ОБЛАСТИ

Система баз данных - это компьютеризированная система хранения записей, т.е. компьютеризированная система, основное назначение которой - хранить информацию, предоставляя пользователям средства её извлечения и модификации [21]. СУБД должна предоставлять доступ к данным любым пользователям, включая и тех, которые практически не имеют и (или) не хотят иметь представления о физическом размещении в памяти данных и их описаний.

Естественно, что проект базы данных надо начинать с анализа предметной области и выявления требований к ней отдельных пользователей (сотрудников организации, для которых создается база данных). Подробнее этот процесс будет рассмотрен ниже, а здесь отметим, что проектирование обычно поручается человеку (группе лиц) - администратору базы данных (АБД). Им может быть, как специально выделенный сотрудник организации, так и будущий пользователь базы данных, достаточно хорошо знакомый с машинной обработкой данных. Объединяя частные представления о содержимом базы данных, полученные в результате опроса пользователей, и свои представления о данных, которые могут потребоваться в будущих приложениях, АБД сначала создает обобщенное неформальное описание создаваемой базы данных. Это описание, выполненное с использованием естественного языка, математических формул, таблиц, графиков и других средств, понятных всем людям, работающих над проектированием базы данных, называют инфологической моделью данных [22].

Инфологическая модель должна быть отображена в компьютеро-ориентированную даталогическую модель, "понятную" СУБД. В процессе развития теории и практического использования баз данных, а также средств вычислительной техники создавались СУБД, поддерживающие различные даталогические модели

Сначала стали использовать иерархические даталогические модели. Простота организации, наличие заранее заданных связей между сущностями, сходство с физическими моделями данных позволяли добиваться приемлемой производительности иерархических СУБД на медленных ЭВМ с весьма ограниченными объемами памяти. Но, если данные не имели древовидной структуры, то возникала масса сложностей при построении иерархической модели и желании добиться нужной производительности.

Сетевые модели также создавались для мало ресурсных ЭВМ. Это достаточно сложные структуры, состоящие из "наборов" - поименованных двухуровневых деревьев. "Наборы" соединяются с помощью "записей-связок", образуя цепочки и т.д. При разработке сетевых моделей было выдумано множество "маленьких хитростей", позволяющих увеличить производительность СУБД, но существенно усложнивших последние. Прикладной программист должен знать массу терминов, изучить несколько внутренних языков СУБД, детально представлять логическую структуру базы данных для осуществления навигации среди различных экземпляров, наборов, записей и т.п. Один из разработчиков операционной системы UNIX сказал "Сетевая база - это самый верный способ потерять данные".

Сложность практического использования иерархических и сетевых СУБД заставляла искать иные способы представления данных. В конце 60-х годов появились СУБД на основе инвертированных файлов, отличающиеся простотой организации и наличием весьма удобных языков манипулирования данными. Однако такие СУБД обладают рядом ограничений на количество файлов для хранения данных, количество связей между ними, длину записи и количество ее полей. Сегодня наиболее распространены реляционные модели.

Из структуры виртуальной модели на рисунке 5.1 видно, что база данных напрямую взаимодействует как с предметной областью, так и с моделирующим блоком. Так как важно соблюдать синергию между теоретической частью курса «Представление знаний в информационных системах» и тем материалом, который выдаётся в виртуальной модели. Оба этих объёма данных должны удовлетворять всем вышеизложенным аспектам баз данных.

Рисунок 5.1 - структурная схема виртуальной модели предметной области

6. РАЗРАБОТКА И РЕАЛИЗАЦИЯ ПРОГРАММНЫХ МОДУЛЕЙ


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

 

.1 Описание программных модулей на языке JavaScript


Описание некоторых программных модулей на языке JavaScript (vJavaScript):

)        модуль внутриигровых событий (описание взаимодействий игрока с игровыми объектами):

const OFFLINE_DATA_FILE = "offline.js";CACHE_NAME_PREFIX = "c2offline";BROADCASTCHANNEL_NAME = "offline";CONSOLE_PREFIX = "[SW] ";

// Create a BroadcastChannel if supported.broadcastChannel = (typeof BroadcastChannel === "undefined" ? null: new BroadcastChannel(BROADCASTCHANNEL_NAME));

// Utility methodsPostBroadcastMessage(o)

{

                if (!broadcastChannel)

                               return;                   // not supported

                // Impose artificial (and arbitrary!) delay of 3 seconds to make sure client is listening by the time the message is sent.

                // Note we could remove the delay on some messages, but then we create a race condition where sometimes messages can arrive

                // in the wrong order (e.g. "update ready" arrives before "started downloading update"). So to keep the consistent ordering,

                // delay all messages by the same amount.

                setTimeout(() => broadcastChannel.postMessage(o), 3000);

};Broadcast(type)

{

                PostBroadcastMessage({

                               "type": type

                });

};BroadcastDownloadingUpdate(version)

{

                PostBroadcastMessage({

                               "type": "downloading-update",

                               "version": version

                });

}BroadcastUpdateReady(version)

{

                PostBroadcastMessage({

                               "type": "update-ready",

                               "version": version

                });

}GetCacheBaseName()

{

                // Include the scope to avoid name collisions with any other SWs on the same origin.

                // e.g. "c2offline-https://example.com/foo/" (won't collide with anything under bar/)

                return CACHE_NAME_PREFIX + "-" + self.registration.scope;

};GetCacheVersionName(version)

{

                // Append the version number to the cache name.

                // e.g. "c2offline-https://example.com/foo/-v2"

                return GetCacheBaseName() + "-v" + version;

};

// Return caches.keys() filtered down to just caches we're interested in (with the right base name).

// This filters out caches from unrelated scopes.GetAvailableCacheNames()

{

                return caches.keys()

                .then(cacheNames =>

                {

                               const cacheBaseName = GetCacheBaseName();

                               return cacheNames.filter(n => n.startsWith(cacheBaseName));

                });

};

// Identify if an update is pending, which is the case when we have 2 or more available caches.

// One must be an update that is waiting, since the next navigate that does an upgrade will

// delete all the old caches leaving just one currently-in-use cache.IsUpdatePending()

{

                return GetAvailableCacheNames()

                .then(availableCacheNames => availableCacheNames.length >= 2);

};

// Automatically deduce the main page URL (e.g. index.html or main.aspx) from the available browser windows.

// This prevents having to hard-code an index page in the file list, implicitly caching it like AppCache did.GetMainPageUrl()

{

                return clients.matchAll({

                               includeUncontrolled: true,

                               type: "window"

                })

                .then(clients =>

                {

                               for (let c of clients)

                               {

                                               // Parse off the scope from the full client URL, e.g. https://example.com/index.html -> index.html

                                               let url = c.url;

                                               if (url.startsWith(self.registration.scope))

                                                               url = url.substring(self.registration.scope.length);

                                              

                                               if (url && url!== "/")                           //./ is also implicitly cached so don't bother returning that

                                               {

                                                               // If the URL is solely a search string, prefix it with / to ensure it caches correctly.

                                                               // e.g. https://example.com/?foo=bar needs to cache as /?foo=bar, not just ?foo=bar.

                                                               if (url.startsWith("?"))

                                                                              url = "/" + url;

                                                              

                                                               return url;

                                               }

                               }

                              

                               return "";                               // no main page URL could be identified

                });

};

// Hack to fetch optionally bypassing HTTP cache until fetch cache options are supported in Chrome (crbug.com/453190)fetchWithBypass(request, bypassCache)

{

                if (typeof request === "string")

                               request = new Request(request);

               

                if (bypassCache)

                {

                               // bypass enabled: add a random search parameter to avoid getting a stale HTTP cache result

                               const url = new URL(request.url);

                               url.search += Math.floor(Math.random() * 1000000);

                               return fetch(url, {

                                               headers: request.headers,

                                               mode: request.mode,

                                               credentials: request.credentials,

                                               redirect: request.redirect,

                                               cache: "no-store"

                               });

                }

                else

                {

                               // bypass disabled: perform normal fetch which is allowed to return from HTTP cache

                               return fetch(request);

                }

};

// Effectively a cache.addAll() that only creates the cache on all requests being successful (as a weak attempt at making it atomic)

// and can optionally cache-bypass with fetchWithBypass in every requestCreateCacheFromFileList(cacheName, fileList, bypassCache)

{

                // Kick off all requests and wait for them all to complete

                return Promise.all(fileList.map(url => fetchWithBypass(url, bypassCache)))

                .then(responses =>

                {

                               // Check if any request failed. If so don't move on to opening the cache.

                               // This makes sure we only open a cache if all requests succeeded.

                               let allOk = true;

                              

                               for (let response of responses)

                               {

                                               if (!response.ok)

                                               {

                                                               allOk = false;

                                                               console.error(CONSOLE_PREFIX + "Error fetching '" + originalUrl + "' (" + response.status + " " + response.statusText + ")");

                                               }

                               }

                              

                               if (!allOk)

                                               throw new Error("not all resources were fetched successfully");

                              

                               // Can now assume all responses are OK. Open a cache and write all responses there.

                               // TODO: ideally we can do this transactionally to ensure a complete cache is written as one atomic operation.

                               // This needs either new transactional features in the spec, or at the very least a way to rename a cache

                               // (so we can write to a temporary name that won't be returned by GetAvailableCacheNames() and then rename it when ready).

                               return caches.open(cacheName)

                               .then(cache =>

                               {

                                               return Promise.all(responses.map(

                                                               (response, i) => cache.put(fileList[i], response)

                                               ));

                               })

                               .catch(err =>

                               {

                                               // Not sure why cache.put() would fail (maybe if storage quota exceeded?) but in case it does,

                                               // clean up the cache to try to avoid leaving behind an incomplete cache.

                                               console.error(CONSOLE_PREFIX + "Error writing cache entries: ", err);

                                               caches.delete(cacheName);

                                               throw err;

                               });

                });

};UpdateCheck(isFirst)

{

                // Always bypass cache when requesting offline.js to make sure we find out about new versions.

                return fetchWithBypass(OFFLINE_DATA_FILE, true)

                .then(r => r.json())

                .then(data =>

                {

                               const version = data.version;

                               let fileList = data.fileList;

                               const currentCacheName = GetCacheVersionName(version);

                              

                               return caches.has(currentCacheName)

                               .then(cacheExists =>

                               {

                                               // Don't recache if there is already a cache that exists for this version. Assume it is complete.

                                               if (cacheExists)

                                               {

                                                               // Log whether we are up-to-date or pending an update.

                                                               return IsUpdatePending()

                                                               .then(isUpdatePending =>

                                                               {

                                                                              if (isUpdatePending)

                                                                              {

                                                                                              console.log(CONSOLE_PREFIX + "Update pending");

                                                                                              Broadcast("update-pending");

                                                                              }

                                                                              else

                                                                              {

                                                                                              console.log(CONSOLE_PREFIX + "Up to date");

                                                                                              Broadcast("up-to-date");

                                                                              }

                                                               });

                                               }

                                              

                                               // Implicitly add the main page URL to the file list, e.g. "index.html", so we don't have to assume a specific name.

                                               return GetMainPageUrl()

                                               .then(mainPageUrl =>

                                               {

                                                               // Prepend the main page URL to the file list if we found one and it is not already in the list.

                                                               // Also make sure we request the base / which should serve the main page.

                                                               fileList.unshift("./");

                                                              

                                                               if (mainPageUrl && fileList.indexOf(mainPageUrl) === -1)

                                                                              fileList.unshift(mainPageUrl);

                                                              

                                                               console.log(CONSOLE_PREFIX + "Caching " + fileList.length + " files for offline use");

                                                              

                                                               if (isFirst)

                                                                              Broadcast("downloading");

                                                               else

                                                                              BroadcastDownloadingUpdate(version);

                                                              

                                                               // Note we don't bypass the cache on the first update check. This is because SW installation and the following

                                                               // update check caching will race with the normal page load requests. For any normal loading fetches that have already

                                                               // completed or are in-flight, it is pointless and wasteful to cache-bust the request for offline caching, since that

                                                               // forces a second network request to be issued when a response from the browser HTTP cache would be fine.

                                                               return CreateCacheFromFileList(currentCacheName, fileList,!isFirst)

                                                               .then(IsUpdatePending)

                                                               .then(isUpdatePending =>

                                                               {

                                                                              if (isUpdatePending)

                                                                              {

                                                                                              console.log(CONSOLE_PREFIX + "All resources saved, update ready");

                                                                                              BroadcastUpdateReady(version);

                                                                              }

                                                                              else

                                                                              {

                                                                                              console.log(CONSOLE_PREFIX + "All resources saved, offline support ready");

                                                                                              Broadcast("offline-ready");

                                                                              }

                                                               });

                                               });

                               });

                })

                .catch(err =>

                {

                               // Update check fetches fail when we're offline, but in case there's any other kind of problem with it, log a warning.

                               console.warn(CONSOLE_PREFIX + "Update check failed: ", err);

                });

};

.addEventListener('install', event =>

{

                // On install kick off an update check to cache files on first use.

                // If it fails we can still complete the install event and leave the SW running, we'll just

                // retry on the next navigate.

                event.waitUntil(

                               UpdateCheck(true)                            // first update

                               .catch(() => null)

                );

});.addEventListener('fetch', event =>

{

                const isNavigateRequest = (event.request.mode === "navigate");

               

                let responsePromise = GetAvailableCacheNames()

                .then(availableCacheNames =>

                {

                               // No caches available: go to network

                               if (!availableCacheNames.length)

                                               return fetch(event.request);

                              

                               // Resolve with the cache name to use.

                               return Promise.resolve().then(() =>

                               {

                                               // Prefer the oldest cache available. This avoids mixed-version responses by ensuring that if a new cache

                                               // is created and filled due to an update check while the page is running, we keep returning resources

                                               // from the original (oldest) cache only.

                                               if (availableCacheNames.length === 1 ||!isNavigateRequest)

                                                               return availableCacheNames[0];

                                              

                                               // We are making a navigate request with more than one cache available. Check if we can expire any old ones.

                                               return clients.matchAll().then(clients =>

                                               {

                                                               // If there are other clients open, don't expire anything yet. We don't want to delete any caches they

                                                               // might be using, which could cause mixed-version responses.

                                                               // TODO: verify client count is as expected in navigate requests.

                                                               // TODO: find a way to upgrade on reloading the only client. Chrome seems to think there are 2 clients in that case.

                                                               if (clients.length > 1)

                                                                              return availableCacheNames[0];

                                                              

                                                               // Identify newest cache to use. Delete all the others.

                                                               let latestCacheName = availableCacheNames[availableCacheNames.length - 1];

                                                               console.log(CONSOLE_PREFIX + "Updating to new version");

                                                              

                                                               return Promise.all(availableCacheNames.slice(0, -1)

                                                                                                                                             .map(c => caches.delete(c)))

                                                               .then(() => latestCacheName);

                                               });

                               }).then(useCacheName =>

                               {

                                               return caches.open(useCacheName)

                                               .then(c => c.match(event.request))

                                               .then(response => response || fetch(event.request));

                               });

                });

                if (isNavigateRequest)

                {

                               // allow the main request to complete, then check for updates

                               event.waitUntil(responsePromise

                               .then(() => UpdateCheck(false)));                  // not first check

                }

                event.respondWith(responsePromise);

});

)              модуль запуска программы в режиме оффлайн (без доступа к интернету):

(function() {

               

                class OfflineClient

                {

                               constructor()

                               {

                                               // Create a BroadcastChannel, if supported.

                                               this._broadcastChannel = (typeof BroadcastChannel === "undefined" ? null: new BroadcastChannel("offline"));

                                              

                                               // Queue of messages received before a message callback is set.

                                               this._queuedMessages = [];

                                              

                                               // The message callback.

                                               this._onMessageCallback = null;

                                              

                                               // If BroadcastChannel is supported, listen for messages.

                                               if (this._broadcastChannel)

                                                               this._broadcastChannel.onmessage = (e => this._OnBroadcastChannelMessage(e));

                               }

                              

                               _OnBroadcastChannelMessage(e)

                               {

                                               // Have a message callback set: just forward the call.

                                               if (this._onMessageCallback)

                                               {

                                                               this._onMessageCallback(e);

                                                               return;

                                               }

                                              

                                               // Otherwise the app hasn't loaded far enough to set a message callback.

                                               // Buffer the incoming messages to replay when the app sets a callback.

                                               this._queuedMessages.push(e);

                               }

                              

                               SetMessageCallback(f)

                               {

                                               this._onMessageCallback = f;

                                              

                                               // Replay any queued messages through the handler, then clear the queue.

                                               for (let e of this._queuedMessages)

                                                               this._onMessageCallback(e);

                                              

                                               this._queuedMessages.length = 0;

                               }

                };

               

                // Create the offline client ASAP so we receive and start queueing any messages the SW broadcasts.

                window.OfflineClientInfo = new OfflineClient();

               

}());

)              движковый модуль описания переменных:cr = {};.plugins_ = {};.behaviors = {};(typeof Object.getPrototypeOf!== "function")

{

                if (typeof "test".__proto__ === "object")

                {

                               Object.getPrototypeOf = function(object) {

                                               return object.__proto__;

                }

                else

                {

                               Object.getPrototypeOf = function(object) {

                                               return object.constructor.prototype;

                               };

                }

}

(function(){

                cr.logexport = function (msg)

                {

                               if (window.console && window.console.log)

                                               window.console.log(msg);

                };

                cr.logerror = function (msg)

                {

                               if (window.console && window.console.error)

                                               window.console.error(msg);

                };

                cr.seal = function(x)

                {

                               return x;

                };

                cr.freeze = function(x)

                {

                               return x;

                };

                cr.is_undefined = function (x)

                {

                               return typeof x === "undefined";

                };

                cr.is_number = function (x)

                {

                               return typeof x === "number";

                };

                cr.is_string = function (x)

                {

                               return typeof x === "string";

                };

                cr.isPOT = function (x)

                {

                               return x > 0 && ((x - 1) & x) === 0;

                };

                cr.nextHighestPowerOfTwo = function(x) {

                               --x;

                               for (var i = 1; i < 32; i <<= 1) {

                                               x = x | x >> i;

                               }

                               return x + 1;

                }

                cr.abs = function (x)

                {

                               return (x < 0 ? -x: x);

                };

                cr.max = function (a, b)

                {

                               return (a > b ? a: b);

                };

                cr.min = function (a, b)

                {

                               return (a < b ? a: b);

                };

                cr.PI = Math.PI;

                cr.round = function (x)

                {

                               return (x + 0.5) | 0;

                };

                cr.floor = function (x)

                {

                               if (x >= 0)

                                               return x | 0;

                               else

                                               return (x | 0) - 1;                  // correctly round down when negative

                };

                cr.ceil = function (x)

                {

                               var f = x | 0;

                               return (f === x ? f: f + 1);

                };

                function Vector2(x, y)

                {

                               this.x = x;

                               this.y = y;

                               cr.seal(this);

                };

                Vector2.prototype.offset = function (px, py)

                {

                               this.x += px;

                               this.y += py;

                               return this;

                };

                Vector2.prototype.mul = function (px, py)

                {

                               this.x *= px;

                               this.y *= py;

                               return this;

                };

                cr.vector2 = Vector2;

                cr.segments_intersect = function(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y)

                {

                               var max_ax, min_ax, max_ay, min_ay, max_bx, min_bx, max_by, min_by;

                               if (a1x < a2x)

                               {

                                               min_ax = a1x;

                                               max_ax = a2x;

                               }

                               else

                               {

                                               min_ax = a2x;

                                               max_ax = a1x;

                               }

                               if (b1x < b2x)

                               {

                                               min_bx = b1x;

                                               max_bx = b2x;

                               }

                               else

                               {

                                               min_bx = b2x;

                                               max_bx = b1x;

                               }

                               if (max_ax < min_bx || min_ax > max_bx)

                                               return false;

                               if (a1y < a2y)

                               {

                                               min_ay = a1y;

                                               max_ay = a2y;

                               }

                               else

                               {

                                               min_ay = a2y;

                                               max_ay = a1y;

                               }

                               if (b1y < b2y)

                               {

                                               min_by = b1y;

                                               max_by = b2y;

                               }

                               else

                               {

                                               min_by = b2y;

                                               max_by = b1y;

                               }

                               if (max_ay < min_by || min_ay > max_by)

                                               return false;

                               var dpx = b1x - a1x + b2x - a2x;

                               var dpy = b1y - a1y + b2y - a2y;

                               var qax = a2x - a1x;

                               var qay = a2y - a1y;

                               var qbx = b2x - b1x;

                               var qby = b2y - b1y;

                               var d = cr.abs(qay * qbx - qby * qax);

                               var la = qbx * dpy - qby * dpx;

                               if (cr.abs(la) > d)

                                               return false;

                               var lb = qax * dpy - qay * dpx;

                               return cr.abs(lb) <= d;

                };

                function Rect(left, top, right, bottom)

                {

                               this.set(left, top, right, bottom);

                               cr.seal(this);

                };

                Rect.prototype.set = function (left, top, right, bottom)

                {

                               this.left = left;

                               this.top = top;

                               this.right = right;

                               this.bottom = bottom;

                };

                Rect.prototype.copy = function (r)

                {

                               this.left = r.left;

                               this.top = r.top;

                               this.right = r.right;

                               this.bottom = r.bottom;

                };

                Rect.prototype.width = function ()

                {

                               return this.right - this.left;

                };

                Rect.prototype.height = function ()

                {

                               return this.bottom - this.top;

                };

                Rect.prototype.offset = function (px, py)

                {

                               this.left += px;

                               this.top += py;

                               this.right += px;

                               this.bottom += py;

                               return this;

                };

                Rect.prototype.normalize = function ()

                {

                               var temp = 0;

                               if (this.left > this.right)

                               {

                                               temp = this.left;

                                               this.left = this.right;

                                               this.right = temp;

                               }

                               if (this.top > this.bottom)

                               {

                                               temp = this.top;

                                               this.top = this.bottom;

                                               this.bottom = temp;

                               }

                };

                Rect.prototype.intersects_rect = function (rc)

                {

                               return!(rc.right < this.left || rc.bottom < this.top || rc.left > this.right || rc.top > this.bottom);

                };

                Rect.prototype.intersects_rect_off = function (rc, ox, oy)

                {

                               return!(rc.right + ox < this.left || rc.bottom + oy < this.top || rc.left + ox > this.right || rc.top + oy > this.bottom);

                };

                Rect.prototype.contains_pt = function (x, y)

                {

                               return (x >= this.left && x <= this.right) && (y >= this.top && y <= this.bottom);

                };

                Rect.prototype.equals = function (r)

                {

                               return this.left === r.left && this.top === r.top && this.right === r.right && this.bottom === r.bottom;

                };

                cr.rect = Rect;

                function Quad()

                {

                               this.tlx = 0;

                               this.tly = 0;

                               this.trx = 0;

                               this.try_ = 0;         // is a keyword otherwise!

                               this.brx = 0;

                               this.bry = 0;

                               this.blx = 0;

                               this.bly = 0;

                               cr.seal(this);

                };

                Quad.prototype.set_from_rect = function (rc)

                {

                               this.tlx = rc.left;

                               this.tly = rc.top;

                               this.trx = rc.right;

                               this.try_ = rc.top;

                               this.brx = rc.right;

                               this.bry = rc.bottom;

                               this.blx = rc.left;

                               this.bly = rc.bottom;

                };

                Quad.prototype.set_from_rotated_rect = function (rc, a)

                {

                               if (a === 0)

                               {

                                               this.set_from_rect(rc);

                               }

                Quad.prototype.offset = function (px, py)

                {

                               this.tlx += px;

                               this.tly += py;

                               this.trx += px;

                               this.try_ += py;

                               this.brx += px;

                               this.bry += py;

                               this.blx += px;

                               this.bly += py;

                               return this;

                };

                var minresult = 0;

                var maxresult = 0;

                function minmax4(a, b, c, d)

                {

                               if (a < b)

                               {

                                               if (c < d)

                                               {

                                                               if (a < c)

                                                                              minresult = a;

                                                               else

                                                                              minresult = c;

                                                               if (b > d)

                                                                              maxresult = b;

                                                               else

                                                                              maxresult = d;

                                               }

                                               else

                                               {

                                                               if (a < d)

                                                                              minresult = a;

                                                               else

                                                                              minresult = d;

                                                               if (b > c)

                                                                              maxresult = b;

                                                               else

                                                                              maxresult = c;

                                               }

                               }

                               else

                                {

                                               if (c < d)

                                               {

                                                               if (b < c)

                                                                              minresult = b;

                                                               else

                                                                              minresult = c;

                                                               if (a > d)

                                                                              maxresult = a;

                                                               else

                                                                              maxresult = d;

                                               }

                                               else

                                               {

                                                               if (b < d)

                                                                              minresult = b;

                                                               else

                                                                              minresult = d;

                                                               if (a > c)

                                                                              maxresult = a;

                                                               else

                                                                              maxresult = c;

                                               }

                               }

                };

                Quad.prototype.bounding_box = function (rc)

                {

                               minmax4(this.tlx, this.trx, this.brx, this.blx);

                               rc.left = minresult;

                               rc.right = maxresult;

                               minmax4(this.tly, this.try_, this.bry, this.bly);

                                rc.top = minresult;

                               rc.bottom = maxresult;

 

.2 Отладка и экспериментальное тестирование модулей


Тестирование программного обеспечения - процесс исследования программного обеспечения с целью получения информации о качестве продукта, выявление в нем ошибок.

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

В тестировании программного обеспечения изучаются внутренние элементы программы и связи между ними. Объектом тестирования является внутреннее поведение программы.

При проверке формируются тестовые варианты, в которых:

- гарантируется проверка всех независимых маршрутов программы;

выполнение всех циклов в пределах их границ и диапазонов;

проход ветви имеет значение True или False для всех логических решений;

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

Однако, при запуске (работе) программы все используемые процедуры и функции выполняются одновременно и полностью. Это не позволяет провести нам структурное и функциональное тестирование программы, требующих соблюдения чёткой логики исполнения программы. В данном же случае, каждая отдельно взятая функция является простейшей функцией, тестирование которой не требует трудоёмких рассчётов. Общая же логика программы зависит исключительно от действий пользователя и не придерживается чёткой логике.

В связи с этой особенностью компелирования кода, большую роль необходимо уделить практическому тестированию.

Различают несколько типов ошибок. первый тип - синтаксические ошибки. С ними обычно просто. Синтаксические ошибки видны сразу, интерпретатор сам выдаёт сообщение об их наличии. Или же функция просто не работает. В любом случае такие вещи очевидны и выявляются почти всегда сразу. Намного сложнее со вторым типом - логические ошибки. Эти ошибки возникают, например, когда перепутан логический оператор, или не учтены или неправильно учтены какие-либо условия. Такие ошибки сразу не очевидны, и выявить их очень непросто. Логическая ошибка может проявить себя спустя несколько месяцев или даже несколько лет после сдачи проекта. Тестирование и отладка сведут число таких ошибок к минимуму.

Из особенностей тестирования и отладки: тестером не имеет право быть человек, который программировал тестируемый проект, но за отладку, как раз наоборот, должен отвечать тот, кто проект создавал. Задача тестировщика проста - искать ошибки.

Тестеровщик, проверевший данную программу не обнаружил синтаксические и логические ошибки.

Так же было проведено тестирование на различных рабочих станциях. Результаты этого тестирования предоставлены в таблице 8.1.

Таблица 8.1 - Результаты тестирования

Номер тестировщика

Характеристики компьютера

Оценка программы


ОС

Процессор

ОЗУ (ГБ)

графика

быстродействие

1

2

3

4

5

6

7

1

Windows XP

Intel core 2dou 1,5 ГГц

NVidia GeForce 5200 128 МБ

1

6

7

2

Windows 7

Intel i5 2.9 ГГц

NVidia GeForce 410М 512 МБ

3

9

10

3

Windows Vista

AMD 3.2 ГГц

NVidia GeForce 520М 512 МБ

4

8

9


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

ЗАКЛЮЧЕНИЕ

В современном мире всё больше внимания уделяется тому, чтобы с первых секунд привлечь внимание потенциального пользователя или покупателя. Мало кого можно сразу заинтересовать богатым внутренним миром. Как много хороших идей погибло только из-за того, что инвесторам не приглянулся внешний вид. Студент - это такой же инвестор. Он вкладывает своё время и усилия в изучение того, практическая польза чего не кажется очевидной. Однако, если вложиться в «прибыльное дело», то наградой за это будут полезные знания и неоценимый опыт. Именно таким «прибыльным делом» и является дисциплина «Представление знаний в информационных системах».

Данная выпускная работа направлена не просто на то, чтобы научить новых студентов, подать им материал и закрепить его у них в головах; но ещё и на то, чтобы они сами этого захотели, чтобы им было интересно и весело изучать новую для них дисциплину.

Игровая форма, баланс между увлечением и полезностью поможет лучше воспринять материал, так как в данном случае уроки по предметной области «Представление знаний в информационных системах» точно не будут перемешиваться в голове со множеством похожих дисциплин «как-то там связанных с компьютером», так говорят сами студенты. Будет задействована не только моторная память, но и образная, и даже эмоциональная.

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

Виртуальная модель предметной области «Представление знаний в информационных системах» - это лишь первый шаг к переходу на совершенно новый уровень обучения. Прогресс движется всё быстрее, виртуальная реальность и дополненная реальность - это уже не фантазии, а действительность. Образовательная система должна не отставать, а даже наоборот - быть на острие технологического развития. Иначе может оказаться так, что обучающийся несколько лет студент выйдет из стен родного университета совершенно неподготовленным к современным техническим реалиям мира. Этого можно избежать благодаря таким проектам, как данная выпускная квалификационная работа.

СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ

1 Лопатина, Т.М. Так ли безопасны компьютерные игры? /Т.М. Лопатина //Высшее образование сегодня. 2006. - №9. - С. 34-37.

Википедия: свободная энциклопедия [Электронный ресурс]. - Режим доступа: https://ru.wikipedia.org/wiki/Представление_знаний

Анализ данных и процессов: учеб. пособие для вузов / А. А. Барсегян, М. С. Куприянов, И. И. Холод [и др.]. - СПб.: БХВ-Петербург, 2009. - 512 с.

Rule-Based Expert Systems: The MYCIN Experiments of the Stanford Heuristic Programming Project

Википедия: свободная энциклопедия [Электронный ресурс]. - Режим доступа: https://ru.wikipedia.org/wiki/Индустрия_компьютерных_игр

SUPERDATA: аналитическая компания [Электронный ресурс]. - Режим доступа: https://www.superdataresearch.com

Википедия: свободная энциклопедия [Электронный ресурс]. - Режим доступа: https://ru.wikipedia.org/wiki/Игры_для_социальных_сетей

Grand Theft Auto V - Rockstar Games [Электронный ресурс]. - Режим доступа: www.rockstargames.com/v/order

Википедия: свободная энциклопедия [Электронный ресурс]. - Режим доступа: https://ru.wikipedia.org/wiki/Классификация_компьютерных_игр

Карева, Г.В. Компьютерные игры как средство развития гностических способностей студентов в ВУЗе / Г.В. Карева // Вестник Брянского государственного технического университета. - 2008. - №4. - С.149-155.

Муравьева, Г.Е. Проектирование технологий обучения / Г.Е. Муравьева // Вестник Брянского государственного технического университета. - 2008. - №4. - С.149-155.

Дуолинго: самоучитель иностранных языков [Электронный ресурс]. - Режим доступа: https://ru.duolingo.com

Карлсон: математическая игра [Электронный ресурс]. - Режим доступа: http://metalspace.ru/games/mathgames/757-mathcarlson.html

Плюсы и минусы дистанционного образования [Электронный ресурс]. - Режим доступа: http://moeobrazovanie.ru

Виртуальная академия [Электронный ресурс]. - Режим доступа: http://vacademia.com/site/info

DevBy: аналитическая ИТ компания [Электронный ресурс]. - Режим доступа: https://dev.by/lenta/main/top-12-yazykov-programmirovaniya-dlya-novichkov-v-2015-godu

Маккоу, А. Веб-приложения на JavaScript / Алекс Маккоу - СПб.: Питер, 2012. - 288 с.

Гаврилова, T.A., Хорошевский, В.Ф. Базы знаний интеллектуальных систем. - Спб: Питер, 2000. - 384 с.

Мобильная разработка с Corona SDK [Электронный ресурс]. - Режим доступа: https://habrahabr.ru/post/134480

Википедия: свободная энциклопедия [Электронный ресурс]. - Режим доступа: https://ru.wikipedia.org/wiki/Construct_2

Макаров, А.С., Лисовский, К.Ю. Базы данных. Введение в теорию и методологию: Учебник - М.: Финансы и статистика, 2004. - 512 с.

Дейт, К. Дж. Введение в системы баз данных / К. Дж. Дейт - М.: Издательский дом “Вильямс”, 2001. - 1072 с.

ПРИЛОЖЕНИЕ

"use strict";

OFFLINE_DATA_FILE = "offline.js";CACHE_NAME_PREFIX = "c2offline";BROADCASTCHANNEL_NAME = "offline";CONSOLE_PREFIX = "[SW] ";

// Create a BroadcastChannel if supported.broadcastChannel = (typeof BroadcastChannel === "undefined" ? null: new BroadcastChannel(BROADCASTCHANNEL_NAME));

//////////////////////////////////////

// Utility methodsPostBroadcastMessage(o)

{

                if (!broadcastChannel)

                               return;                   // not supported

               

                // Impose artificial (and arbitrary!) delay of 3 seconds to make sure client is listening by the time the message is sent.

                // Note we could remove the delay on some messages, but then we create a race condition where sometimes messages can arrive

                // in the wrong order (e.g. "update ready" arrives before "started downloading update"). So to keep the consistent ordering,

                // delay all messages by the same amount.

                setTimeout(() => broadcastChannel.postMessage(o), 3000);

};

Broadcast(type)

{

                PostBroadcastMessage({

                               "type": type

                });

};

BroadcastDownloadingUpdate(version)

{

                PostBroadcastMessage({

                               "type": "downloading-update",

                               "version": version

                });

}

BroadcastUpdateReady(version)

{

                PostBroadcastMessage({

                               "type": "update-ready",

                               "version": version

                });

}

GetCacheBaseName()

{

                // Include the scope to avoid name collisions with any other SWs on the same origin.

                // e.g. "c2offline-https://example.com/foo/" (won't collide with anything under bar/)

                return CACHE_NAME_PREFIX + "-" + self.registration.scope;

};

GetCacheVersionName(version)

{

                // Append the version number to the cache name.

                // e.g. "c2offline-https://example.com/foo/-v2"

                return GetCacheBaseName() + "-v" + version;

};

// Return caches.keys() filtered down to just caches we're interested in (with the right base name).

// This filters out caches from unrelated scopes.GetAvailableCacheNames()

{

                return caches.keys()

                .then(cacheNames =>

                {

                               const cacheBaseName = GetCacheBaseName();

                               return cacheNames.filter(n => n.startsWith(cacheBaseName));

                });

};

// Identify if an update is pending, which is the case when we have 2 or more available caches.

// One must be an update that is waiting, since the next navigate that does an upgrade will

// delete all the old caches leaving just one currently-in-use cache.IsUpdatePending()

{

                return GetAvailableCacheNames()

                .then(availableCacheNames => availableCacheNames.length >= 2);

};

// Automatically deduce the main page URL (e.g. index.html or main.aspx) from the available browser windows.

// This prevents having to hard-code an index page in the file list, implicitly caching it like AppCache did.GetMainPageUrl()

{

                return clients.matchAll({

                               includeUncontrolled: true,

                               type: "window"

                })

                .then(clients =>

                {

                               for (let c of clients)

                               {

                                               // Parse off the scope from the full client URL, e.g. https://example.com/index.html -> index.html

                                               let url = c.url;

                                               if (url.startsWith(self.registration.scope))

                                                               url = url.substring(self.registration.scope.length);

                                              

                                               if (url && url!== "/")                           //./ is also implicitly cached so don't bother returning that

                                               {

                                                               // If the URL is solely a search string, prefix it with / to ensure it caches correctly.

                                                               // e.g. https://example.com/?foo=bar needs to cache as /?foo=bar, not just ?foo=bar.

                                                               if (url.startsWith("?"))

                                                                              url = "/" + url;

                                                              

                                                               return url;

                                               }

                               }

                              

                               return "";                               // no main page URL could be identified

                });

};

// Hack to fetch optionally bypassing HTTP cache until fetch cache options are supported in Chrome (crbug.com/453190)fetchWithBypass(request, bypassCache)

{

                if (typeof request === "string")

                               request = new Request(request);

               

                if (bypassCache)

                {

                               // bypass enabled: add a random search parameter to avoid getting a stale HTTP cache result

                               const url = new URL(request.url);

                               url.search += Math.floor(Math.random() * 1000000);

                               return fetch(url, {

                                               headers: request.headers,

                                               mode: request.mode,

                                               credentials: request.credentials,

                                               redirect: request.redirect,

                                               cache: "no-store"

                               });

                }

                else

                {

                               // bypass disabled: perform normal fetch which is allowed to return from HTTP cache

                               return fetch(request);

                }

};

// Effectively a cache.addAll() that only creates the cache on all requests being successful (as a weak attempt at making it atomic)

// and can optionally cache-bypass with fetchWithBypass in every requestCreateCacheFromFileList(cacheName, fileList, bypassCache)

{

                // Kick off all requests and wait for them all to complete

                return Promise.all(fileList.map(url => fetchWithBypass(url, bypassCache)))

                .then(responses =>

                {

                               // Check if any request failed. If so don't move on to opening the cache.

                               // This makes sure we only open a cache if all requests succeeded.

                               let allOk = true;

                              

                               for (let response of responses)

                               {

                                               if (!response.ok)

                                               {

                                                               allOk = false;

                                                               console.error(CONSOLE_PREFIX + "Error fetching '" + originalUrl + "' (" + response.status + " " + response.statusText + ")");

                                               }

                               }

                              

                               if (!allOk)

                                               throw new Error("not all resources were fetched successfully");

                              

                               // Can now assume all responses are OK. Open a cache and write all responses there.

                               // TODO: ideally we can do this transactionally to ensure a complete cache is written as one atomic operation.

                               // This needs either new transactional features in the spec, or at the very least a way to rename a cache

                               // (so we can write to a temporary name that won't be returned by GetAvailableCacheNames() and then rename it when ready).

                               return caches.open(cacheName)

                               .then(cache =>

                               {

                                               return Promise.all(responses.map(

                                                               (response, i) => cache.put(fileList[i], response)

                                               ));

                               })

                               .catch(err =>

                               {

                                               // Not sure why cache.put() would fail (maybe if storage quota exceeded?) but in case it does,

                                               // clean up the cache to try to avoid leaving behind an incomplete cache.

                                               console.error(CONSOLE_PREFIX + "Error writing cache entries: ", err);

                                               caches.delete(cacheName);

                                               throw err;

                               });

                });

};

UpdateCheck(isFirst)

{

                // Always bypass cache when requesting offline.js to make sure we find out about new versions.

                return fetchWithBypass(OFFLINE_DATA_FILE, true)

                .then(r => r.json())

                .then(data =>

                {

                               const version = data.version;

                               let fileList = data.fileList;

                               const currentCacheName = GetCacheVersionName(version);

                              

                               return caches.has(currentCacheName)

                               .then(cacheExists =>

                               {

                                               // Don't recache if there is already a cache that exists for this version. Assume it is complete.

                                               if (cacheExists)

                                               {

                                                               // Log whether we are up-to-date or pending an update.

                                                               return IsUpdatePending()

                                                               .then(isUpdatePending =>

                                                               {

                                                                              if (isUpdatePending)

                                                                              {

                                                                                              console.log(CONSOLE_PREFIX + "Update pending");

                                                                                              Broadcast("update-pending");

                                                                              }

                                                                              else

                                                                              {

                                                                                              console.log(CONSOLE_PREFIX + "Up to date");

                                                                                              Broadcast("up-to-date");

                                                                              }

                                                               });

                                               }

                                              

                                               // Implicitly add the main page URL to the file list, e.g. "index.html", so we don't have to assume a specific name.

                                               return GetMainPageUrl()

                                               .then(mainPageUrl =>

                                               {

                                                               // Prepend the main page URL to the file list if we found one and it is not already in the list.

                                                               // Also make sure we request the base / which should serve the main page.

                                                               fileList.unshift("./");

                                                              

                                                               if (mainPageUrl && fileList.indexOf(mainPageUrl) === -1)

                                                                              fileList.unshift(mainPageUrl);

                                                              

                                                               console.log(CONSOLE_PREFIX + "Caching " + fileList.length + " files for offline use");

                                                              

                                                               if (isFirst)

                                                                              Broadcast("downloading");

                                                               else

                                                                              BroadcastDownloadingUpdate(version);

                                                              

                                                               // Note we don't bypass the cache on the first update check. This is because SW installation and the following

                                                               // update check caching will race with the normal page load requests. For any normal loading fetches that have already

                                                               // completed or are in-flight, it is pointless and wasteful to cache-bust the request for offline caching, since that

                                                               // forces a second network request to be issued when a response from the browser HTTP cache would be fine.

                                                               return CreateCacheFromFileList(currentCacheName, fileList,!isFirst)

                                                               .then(IsUpdatePending)

                                                               .then(isUpdatePending =>

                                                               {

                                                                              if (isUpdatePending)

                                                                              {

                                                                                              console.log(CONSOLE_PREFIX + "All resources saved, update ready");

                                                                                              BroadcastUpdateReady(version);

                                                                              }

                                                                              else

                                                                              {

                                                                                              console.log(CONSOLE_PREFIX + "All resources saved, offline support ready");

                                                                                              Broadcast("offline-ready");

                                                                              }

                                                               });

                                               });

                               });

                })

                .catch(err =>

                {

                               // Update check fetches fail when we're offline, but in case there's any other kind of problem with it, log a warning.

                               console.warn(CONSOLE_PREFIX + "Update check failed: ", err);

                });

};

.addEventListener('install', event =>

{

                // On install kick off an update check to cache files on first use.

                // If it fails we can still complete the install event and leave the SW running, we'll just

                // retry on the next navigate.

                event.waitUntil(

                               UpdateCheck(true)                            // first update

                               .catch(() => null)

                );

});

.addEventListener('fetch', event =>

{

                const isNavigateRequest = (event.request.mode === "navigate");

               

                let responsePromise = GetAvailableCacheNames()

                .then(availableCacheNames =>

                {

                               // No caches available: go to network

                               if (!availableCacheNames.length)

                                               return fetch(event.request);

                              

                               // Resolve with the cache name to use.

                               return Promise.resolve().then(() =>

                               {

                                               // Prefer the oldest cache available. This avoids mixed-version responses by ensuring that if a new cache

                                               // is created and filled due to an update check while the page is running, we keep returning resources

                                               // from the original (oldest) cache only.

                                               if (availableCacheNames.length === 1 ||!isNavigateRequest)

                                                               return availableCacheNames[0];

                                              

                                               // We are making a navigate request with more than one cache available. Check if we can expire any old ones.

                                               return clients.matchAll().then(clients =>

                                               {

                                                               // If there are other clients open, don't expire anything yet. We don't want to delete any caches they

                                                               // might be using, which could cause mixed-version responses.

                                                               // TODO: verify client count is as expected in navigate requests.

                                                               // TODO: find a way to upgrade on reloading the only client. Chrome seems to think there are 2 clients in that case.

                                                               if (clients.length > 1)

                                                                              return availableCacheNames[0];

                                                              

                                                               // Identify newest cache to use. Delete all the others.

                                                               let latestCacheName = availableCacheNames[availableCacheNames.length - 1];

                                                               console.log(CONSOLE_PREFIX + "Updating to new version");

                                                              

                                                               return Promise.all(availableCacheNames.slice(0, -1)

                                                                                                                                             .map(c => caches.delete(c)))

                                               });

                               }).then(useCacheName =>

                               {

                                               return caches.open(useCacheName)

                                               .then(c => c.match(event.request))

                                               .then(response => response || fetch(event.request));

                               });

                });

                if (isNavigateRequest)

                {

                               // allow the main request to complete, then check for updates

                               event.waitUntil(responsePromise

                               .then(() => UpdateCheck(false)));                  // not first check

                }

                event.respondWith(responsePromise);

});

// Generated by Construct 2, the HTML5 game and app creator:: http://www.scirra.comcr = {};.plugins_ = {};.behaviors = {};(typeof Object.getPrototypeOf!== "function")

{

                if (typeof "test".__proto__ === "object")

                {

                                Object.getPrototypeOf = function(object) {

                                               return object.__proto__;

                               };

                }

                else

                {

                               Object.getPrototypeOf = function(object) {

                                               return object.constructor.prototype;

                               };

                }

}

(function(){

                cr.logexport = function (msg)

                {

                               if (window.console && window.console.log)

                                               window.console.log(msg);

                };

                cr.logerror = function (msg)

                {

                               if (window.console && window.console.error)

                                               window.console.error(msg);

                };

                cr.seal = function(x)

                {

                               return x;

                };

                cr.freeze = function(x)

                {

                               return x;

                };

                cr.is_undefined = function (x)

                {

                               return typeof x === "undefined";

                };

                cr.is_number = function (x)

                {

                               return typeof x === "number";

                };

                cr.is_string = function (x)

                {

                               return typeof x === "string";

                };

                cr.isPOT = function (x)

                {

                               return x > 0 && ((x - 1) & x) === 0;

                };

                cr.nextHighestPowerOfTwo = function(x) {

                               --x;

                               for (var i = 1; i < 32; i <<= 1) {

                                               x = x | x >> i;

                               }

                               return x + 1;

                }

                cr.abs = function (x)

                {

                               return (x < 0 ? -x: x);

                };

                cr.max = function (a, b)

                {

                               return (a > b ? a: b);

                };

                cr.min = function (a, b)

                {

                               return (a < b ? a: b);

                };

                cr.PI = Math.PI;

                cr.round = function (x)

                {

                               return (x + 0.5) | 0;

                };

                cr.floor = function (x)

                {

                               if (x >= 0)

                                               return x | 0;

                               else

                                               return (x | 0) - 1;                  // correctly round down when negative

                };

                cr.ceil = function (x)

                {

                               var f = x | 0;

                               return (f === x ? f: f + 1);

                };

                function Vector2(x, y)

                {

                               this.x = x;

                               this.y = y;

                               cr.seal(this);

                };

                Vector2.prototype.offset = function (px, py)

                {

                               this.x += px;

                                this.y += py;

                               return this;

                };

                Vector2.prototype.mul = function (px, py)

                {

                               this.x *= px;

                               this.y *= py;

                               return this;

                };

                cr.vector2 = Vector2;

                cr.segments_intersect = function(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y)

                {

                               var max_ax, min_ax, max_ay, min_ay, max_bx, min_bx, max_by, min_by;

                               if (a1x < a2x)

                               {

                                               min_ax = a1x;

                                               max_ax = a2x;

                               }

                               else

                               {

                                               min_ax = a2x;

                                               max_ax = a1x;

                               }

                               if (b1x < b2x)

                               {

                                               min_bx = b1x;

                                               max_bx = b2x;

                               }

                               else

                               {

                                               min_bx = b2x;

                                               max_bx = b1x;

                               }

                               if (max_ax < min_bx || min_ax > max_bx)

                                               return false;

                               if (a1y < a2y)

                               {

                                               min_ay = a1y;

                                               max_ay = a2y;

                               }

                               else

                               {

                                               min_ay = a2y;

                                               max_ay = a1y;

                               }

                               if (b1y < b2y)

                               {

                                               min_by = b1y;

                                               max_by = b2y;

                               }

                               else

                               {

                                               min_by = b2y;

                                               max_by = b1y;

                               }

                               if (max_ay < min_by || min_ay > max_by)

                                               return false;

                               var dpx = b1x - a1x + b2x - a2x;

                               var dpy = b1y - a1y + b2y - a2y;

                               var qax = a2x - a1x;

                               var qay = a2y - a1y;

                               var qbx = b2x - b1x;

                               var qby = b2y - b1y;

                               var d = cr.abs(qay * qbx - qby * qax);

                               var la = qbx * dpy - qby * dpx;

                               if (cr.abs(la) > d)

                                               return false;

                               var lb = qax * dpy - qay * dpx;

                               return cr.abs(lb) <= d;

                };

                function Rect(left, top, right, bottom)

                {

                               this.set(left, top, right, bottom);

                               cr.seal(this);

                };

                Rect.prototype.set = function (left, top, right, bottom)

                {

                               this.left = left;

                               this.top = top;

                               this.right = right;

                               this.bottom = bottom;

                };

                Rect.prototype.copy = function (r)

                {

                               this.left = r.left;

                               this.top = r.top;

                               this.right = r.right;

                               this.bottom = r.bottom;

                };

                Rect.prototype.width = function ()

                {

                               return this.right - this.left;

                };

                Rect.prototype.height = function ()

                {

                               return this.bottom - this.top;

                };

                Rect.prototype.offset = function (px, py)

                {

                               this.left += px;

                               this.top += py;

                               this.right += px;

                               this.bottom += py;

                               return this;

                };

                Rect.prototype.normalize = function ()

                {

                               var temp = 0;

                               if (this.left > this.right)

                               {

                                               temp = this.left;

                                               this.left = this.right;

                                               this.right = temp;

                               }

                               if (this.top > this.bottom)

                               {

                                               temp = this.top;

                                               this.top = this.bottom;

                                               this.bottom = temp;

                               }

                };

                Rect.prototype.intersects_rect = function (rc)

                {

                               return!(rc.right < this.left || rc.bottom < this.top || rc.left > this.right || rc.top > this.bottom);

                };

                Rect.prototype.intersects_rect_off = function (rc, ox, oy)

                {

                               return!(rc.right + ox < this.left || rc.bottom + oy < this.top || rc.left + ox > this.right || rc.top + oy > this.bottom);

                };

                Rect.prototype.contains_pt = function (x, y)

                {

                               return (x >= this.left && x <= this.right) && (y >= this.top && y <= this.bottom);

                };

                Rect.prototype.equals = function (r)

                {

                               return this.left === r.left && this.top === r.top && this.right === r.right && this.bottom === r.bottom;

                };

                cr.rect = Rect;

                function Quad()

                {

                               this.tlx = 0;

                               this.tly = 0;

                               this.trx = 0;

                               this.try_ = 0;         // is a keyword otherwise!

                               this.brx = 0;

                               this.bry = 0;

                               this.blx = 0;

                               this.bly = 0;

                               cr.seal(this);

                };

                Quad.prototype.set_from_rect = function (rc)

                {

                               this.tlx = rc.left;

                               this.tly = rc.top;

                               this.trx = rc.right;

                               this.try_ = rc.top;

                               this.brx = rc.right;

                               this.bry = rc.bottom;

                               this.blx = rc.left;

                               this.bly = rc.bottom;

                };

                Quad.prototype.set_from_rotated_rect = function (rc, a)

                {

                               if (a === 0)

                               {

                                               this.set_from_rect(rc);

                               }

                               else

                               {

                                               var sin_a = Math.sin(a);

                                               var cos_a = Math.cos(a);

                                               var left_sin_a = rc.left * sin_a;

                                               var top_sin_a = rc.top * sin_a;

                                               var right_sin_a = rc.right * sin_a;

                                               var bottom_sin_a = rc.bottom * sin_a;

                                               var left_cos_a = rc.left * cos_a;

                                               var top_cos_a = rc.top * cos_a;

                                               var right_cos_a = rc.right * cos_a;

                                               var bottom_cos_a = rc.bottom * cos_a;

                                               this.tlx = left_cos_a - top_sin_a;

                                               this.tly = top_cos_a + left_sin_a;

                                               this.trx = right_cos_a - top_sin_a;

                                               this.try_ = top_cos_a + right_sin_a;

                                               this.brx = right_cos_a - bottom_sin_a;

                                               this.bry = bottom_cos_a + right_sin_a;

                                               this.blx = left_cos_a - bottom_sin_a;

                                               this.bly = bottom_cos_a + left_sin_a;

                               }

                };

                Quad.prototype.offset = function (px, py)

                {

                               this.tlx += px;

                               this.tly += py;

                               this.trx += px;

                               this.try_ += py;

                               this.brx += px;

                               this.bry += py;

                               this.blx += px;

                               this.bly += py;

                               return this;

                };

                var minresult = 0;

                var maxresult = 0;

                function minmax4(a, b, c, d)

                {

                               if (a < b)

                               {

                                               if (c < d)

                                               {

                                                               if (a < c)

                                                                              minresult = a;

                                                               else

                                                                              minresult = c;

                                                               if (b > d)

                                                                              maxresult = b;

                                                               else

                                                                              maxresult = d;

                                               }

                                               else

                                               {

                                                               if (a < d)

                                                                              minresult = a;

                                                               else

                                                                              minresult = d;

                                                               if (b > c)

                                                                              maxresult = b;

                                                               else

                                                                              maxresult = c;

                                               }

                               }

                               else

                               {

                                               if (c < d)

                                               {

                                                               if (b < c)

                                                                              minresult = b;

                                                               else

                                                                              minresult = c;

                                                               if (a > d)

                                                               else

                                                                              maxresult = d;

                                               }

                                               else

                                               {

                                                               if (b < d)

                                                                              minresult = b;

                                                               else

                                                                              minresult = d;

                                                               if (a > c)

                                                                              maxresult = a;

                                                               else

                                                                              maxresult = c;

                                               }

                               }

                };

                Quad.prototype.bounding_box = function (rc)

                {

                               minmax4(this.tlx, this.trx, this.brx, this.blx);

                               rc.left = minresult;

                               rc.right = maxresult;

                               minmax4(this.tly, this.try_, this.bry, this.bly);

                               rc.top = minresult;

                               rc.bottom = maxresult;

                };

                Quad.prototype.contains_pt = function (x, y)

                {

                               var tlx = this.tlx;

                               var tly = this.tly;

                               var v0x = this.trx - tlx;

                               var v0y = this.try_ - tly;

                               var v1x = this.brx - tlx;

                               var v1y = this.bry - tly;

                               var v2x = x - tlx;

                               var v2y = y - tly;

                               var dot00 = v0x * v0x + v0y * v0y

                               var dot01 = v0x * v1x + v0y * v1y

                               var dot02 = v0x * v2x + v0y * v2y

                               var dot11 = v1x * v1x + v1y * v1y

                               var dot12 = v1x * v2x + v1y * v2y

                               var invDenom = 1.0 / (dot00 * dot11 - dot01 * dot01);

                               var u = (dot11 * dot02 - dot01 * dot12) * invDenom;

                               var v = (dot00 * dot12 - dot01 * dot02) * invDenom;

                               if ((u >= 0.0) && (v > 0.0) && (u + v < 1))

                                               return true;

                               v0x = this.blx - tlx;

                               v0y = this.bly - tly;

                               var dot00 = v0x * v0x + v0y * v0y

                               var dot01 = v0x * v1x + v0y * v1y

                               var dot02 = v0x * v2x + v0y * v2y

                               invDenom = 1.0 / (dot00 * dot11 - dot01 * dot01);

                               u = (dot11 * dot02 - dot01 * dot12) * invDenom;

                               v = (dot00 * dot12 - dot01 * dot02) * invDenom;

                               return (u >= 0.0) && (v > 0.0) && (u + v < 1);

                };

                Quad.prototype.at = function (i, xory)

                {

                               if (xory)

                               {

                                               switch (i)

                                               {

                                                               case 0: return this.tlx;

                                                               case 1: return this.trx;

                                                               case 2: return this.brx;

                                                               case 3: return this.blx;

                                                               case 4: return this.tlx;

                                                               default: return this.tlx;

                                               }

                               }

                               else

                               {

                                               switch (i)

                                               {

                                                               case 0: return this.tly;

                                                               case 1: return this.try_;

                                                               case 2: return this.bry;

                                                               case 3: return this.bly;

                                                               case 4: return this.tly;

                                                               default: return this.tly;

                                               }

                               }

                };

                Quad.prototype.midX = function ()

                {

                               return (this.tlx + this.trx + this.brx + this.blx) / 4;

                };

                Quad.prototype.midY = function ()

                {

                               return (this.tly + this.try_ + this.bry + this.bly) / 4;

                };

                Quad.prototype.intersects_segment = function (x1, y1, x2, y2)

                {

                               if (this.contains_pt(x1, y1) || this.contains_pt(x2, y2))

                                               return true;

                               var a1x, a1y, a2x, a2y;

                               var i;

                               for (i = 0; i < 4; i++)

                               {

                                               a1x = this.at(i, true);

                                               a1y = this.at(i, false);

                                               a2x = this.at(i + 1, true);

                                               a2y = this.at(i + 1, false);

                                               if (cr.segments_intersect(x1, y1, x2, y2, a1x, a1y, a2x, a2y))

                                                               return true;

                               }

                               return false;

                };

                Quad.prototype.intersects_quad = function (rhs)

                {

                               var midx = rhs.midX();

                               var midy = rhs.midY();

                               if (this.contains_pt(midx, midy))

                                               return true;

                               midx = this.midX();

                               midy = this.midY();

                               if (rhs.contains_pt(midx, midy))

                                               return true;

                               var a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y;

                               var i, j;

                               for (i = 0; i < 4; i++)

                               {

                                               for (j = 0; j < 4; j++)

                                               {

                                                               a1x = this.at(i, true);

                                                               a1y = this.at(i, false);

                                                               a2x = this.at(i + 1, true);

                                                               a2y = this.at(i + 1, false);

                                                               b1x = rhs.at(j, true);

                                                               b1y = rhs.at(j, false);

                                                               b2x = rhs.at(j + 1, true);

                                                               b2y = rhs.at(j + 1, false);

                                                               if (cr.segments_intersect(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y))

                                                                              return true;

                                               }

                               }

                               return false;

                };

                cr.quad = Quad;

                cr.RGB = function (red, green, blue)

                {

                               return Math.max(Math.min(red, 255), 0)

                                                | (Math.max(Math.min(green, 255), 0) << 8)

                                                | (Math.max(Math.min(blue, 255), 0) << 16);

                };

                cr.GetRValue = function (rgb)

                {

                               return rgb & 0xFF;

                };

                cr.GetGValue = function (rgb)

                {

                               return (rgb & 0xFF00) >> 8;

                };

                cr.GetBValue = function (rgb)

                {

                               return (rgb & 0xFF0000) >> 16;

                };

                cr.shallowCopy = function (a, b, allowOverwrite)

                {

                               var attr;

                               for (attr in b)

                               {

                                               if (b.hasOwnProperty(attr))

                                               {

;

                                                               a[attr] = b[attr];

                                               }

                               }

                               return a;

                };

                cr.arrayRemove = function (arr, index)

                {

                               var i, len;

                               index = cr.floor(index);

                               if (index < 0 || index >= arr.length)

                                               return;                                                                                                  // index out of bounds

                               for (i = index, len = arr.length - 1; i < len; i++)

                                               arr[i] = arr[i + 1];

                               cr.truncateArray(arr, len);

                };

                cr.truncateArray = function (arr, index)

                {

                               arr.length = index;

                };

                cr.clearArray = function (arr)

                {

                               cr.truncateArray(arr, 0);

                };

                cr.shallowAssignArray = function (dest, src)

                {

                               cr.clearArray(dest);

                               var i, len;

                               for (i = 0, len = src.length; i < len; ++i)

                                               dest[i] = src[i];

                };

                cr.appendArray = function (a, b)

                {

                               a.push.apply(a, b);

                };

                cr.fastIndexOf = function (arr, item)

                {

                               var i, len;

                               for (i = 0, len = arr.length; i < len; ++i)

                               {

                                               if (arr[i] === item)

                                                               return i;

                               }

                               return -1;

                };

                cr.arrayFindRemove = function (arr, item)

                {

                               var index = cr.fastIndexOf(arr, item);

                               if (index!== -1)

                                               cr.arrayRemove(arr, index);

                };

                cr.clamp = function(x, a, b)

                {

                               if (x < a)

                                               return a;

                               else if (x > b)

                                               return b;

                               else

                                               return x;

                };

                cr.to_radians = function(x)

                {

                               return x / (180.0 / cr.PI);

                };

                cr.to_degrees = function(x)

                {

                               return x * (180.0 / cr.PI);

                };

                cr.clamp_angle_degrees = function (a)

                {

                               a %= 360; // now in (-360, 360) range

                               if (a < 0)

                                               a += 360; // now in [0, 360) range

                               return a;

                };

                cr.clamp_angle = function (a)

                {

                               a %= 2 * cr.PI; // now in (-2pi, 2pi) range

                               if (a < 0)

                                               a += 2 * cr.PI; // now in [0, 2pi) range

                               return a;

                };

                cr.to_clamped_degrees = function (x)

                {

                               return cr.clamp_angle_degrees(cr.to_degrees(x));

                };

                cr.to_clamped_radians = function (x)

                {

                               return cr.clamp_angle(cr.to_radians(x));

                };

                cr.angleTo = function(x1, y1, x2, y2)

                {

                               var dx = x2 - x1;dy = y2 - y1;

                               return Math.atan2(dy, dx);

                };

                cr.angleDiff = function (a1, a2)

                {

                               if (a1 === a2)

                                               return 0;

                               var s1 = Math.sin(a1);

                               var c1 = Math.cos(a1);

                               var s2 = Math.sin(a2);

                               var c2 = Math.cos(a2);

                               var n = s1 * s2 + c1 * c2;

                               if (n >= 1)

                                               return 0;

                               if (n <= -1)

                                               return cr.PI;

                               return Math.acos(n);

                };

                cr.angleRotate = function (start, end, step)

                {

                               var ss = Math.sin(start);

                               var cs = Math.cos(start);

                               var se = Math.sin(end);

                               var ce = Math.cos(end);

                               if (Math.acos(ss * se + cs * ce) > step)

                               {

                                               if (cs * se - ss * ce > 0)

                                                               return cr.clamp_angle(start + step);

                                               else

                                                               return cr.clamp_angle(start - step);

                               }

                               else

                                               return cr.clamp_angle(end);

                };

                cr.angleClockwise = function (a1, a2)

                {

                               var s1 = Math.sin(a1);

                               var c1 = Math.cos(a1);

                               var s2 = Math.sin(a2);

                               var c2 = Math.cos(a2);

                               return c1 * s2 - s1 * c2 <= 0;

                };

                cr.rotatePtAround = function (px, py, a, ox, oy, getx)

                {

                               if (a === 0)

                                               return getx ? px: py;

                               var sin_a = Math.sin(a);

                               var cos_a = Math.cos(a);

                               px -= ox;

                               py -= oy;

                               var left_sin_a = px * sin_a;

                               var top_sin_a = py * sin_a;

                               var left_cos_a = px * cos_a;

                               var top_cos_a = py * cos_a;

                               px = left_cos_a - top_sin_a;

                               py = top_cos_a + left_sin_a;

                               px += ox;

                               py += oy;

                               return getx ? px: py;

                }

                cr.distanceTo = function(x1, y1, x2, y2)

                {

                               var dx = x2 - x1;dy = y2 - y1;

                               return Math.sqrt(dx*dx + dy*dy);

                };

                cr.xor = function (x, y)

                {

                               return!x!==!y;

                };

                cr.lerp = function (a, b, x)

                {

                               return a + (b - a) * x;

                };

                cr.unlerp = function (a, b, c)

                {

                               if (a === b)

                                               return 0;                                // avoid divide by 0

                               return (c - a) / (b - a);

                };

                cr.anglelerp = function (a, b, x)

                {

                               var diff = cr.angleDiff(a, b);

                               if (cr.angleClockwise(b, a))

                               {

                                               return a + diff * x;

                               }

                               else

                               {

                                               return a - diff * x;

                               }

                };

                cr.qarp = function (a, b, c, x)

                {

                               return cr.lerp(cr.lerp(a, b, x), cr.lerp(b, c, x), x);

                };

                {

                               return cr.lerp(cr.qarp(a, b, c, x), cr.qarp(b, c, d, x), x);

                };

                cr.cosp = function (a, b, x)

                {

                               return (a + b + (a - b) * Math.cos(x * Math.PI)) / 2;

                };

                cr.hasAnyOwnProperty = function (o)

                {

                               var p;

                               for (p in o)

                               {

                                               if (o.hasOwnProperty(p))

                                                               return true;

                               }

                               return false;

                };

                cr.wipe = function (obj)

                {

                               var p;

                               for (p in obj)

                               {

                                               if (obj.hasOwnProperty(p))

                                                               delete obj[p];

                               }

                };

                var startup_time = +(new Date());

                cr.performance_now = function()

                {

                               if (typeof window["performance"]!== "undefined")

                               {

                                               var winperf = window["performance"];

                                               if (typeof winperf.now!== "undefined")

                                                               return winperf.now();

                                               else if (typeof winperf["webkitNow"]!== "undefined")

                                                              return winperf["webkitNow"]();

                                               else if (typeof winperf["mozNow"]!== "undefined")

                                                               return winperf["mozNow"]();

                                               else if (typeof winperf["msNow"]!== "undefined")

                                                               return winperf["msNow"]();

                               }

                               return Date.now() - startup_time;

                };

                var isChrome = false;

                var isSafari = false;

                var isiOS = false;

                var isEjecta = false;

                if (typeof window!== "undefined")                // not c2 editor

                {

                               isChrome = /chrome/i.test(navigator.userAgent) || /chromium/i.test(navigator.userAgent);

                               isSafari =!isChrome && /safari/i.test(navigator.userAgent);

                               isiOS = /(iphone|ipod|ipad)/i.test(navigator.userAgent);

                               isEjecta = window["c2ejecta"];

                }

                var supports_set = ((!isSafari &&!isEjecta &&!isiOS) && (typeof Set!== "undefined" && typeof Set.prototype["forEach"]!== "undefined"));

                function ObjectSet_()

                {

                               this.s = null;

                               this.items = null;                                 // lazy allocated (hopefully results in better GC performance)

                               this.item_count = 0;

                               if (supports_set)

                               {

                                               this.s = new Set();

                               }

                               this.values_cache = [];

                               this.cache_valid = true;

                               cr.seal(this);

                };

                ObjectSet_.prototype.contains = function (x)

                {

                               if (this.isEmpty())

                                               return false;

                               if (supports_set)

                                               return this.s["has"](x);

                               else

                                               return (this.items && this.items.hasOwnProperty(x));

                };

                ObjectSet_.prototype.add = function (x)

                {

                               if (supports_set)

                               {

                                               if (!this.s["has"](x))

                                               {

                                                               this.s["add"](x);

                                                               this.cache_valid = false;

                                               }

                               }

                               else

                               {

                                               var str = x.toString();

                                               var items = this.items;

                                               if (!items)

                                               {

                                                               this.items = {};

                                                               this.items[str] = x;

                                                               this.item_count = 1;

                                                               this.cache_valid = false;

                                               }

                                               else if (!items.hasOwnProperty(str))

                                               {

                                                               items[str] = x;

                                                               this.item_count++;

                                                               this.cache_valid = false;

                                               }

                               }

                };

                ObjectSet_.prototype.remove = function (x)

                {

                               if (this.isEmpty())

                                               return;

                               if (supports_set)

                               {

                                               if (this.s["has"](x))

                                               {

                                                               this.s["delete"](x);

                                                               this.cache_valid = false;

                                               }

                               }

                               else if (this.items)

                               {

                                               var str = x.toString();

                                               var items = this.items;

                                               if (items.hasOwnProperty(str))

                                               {

                                                               delete items[str];

                                                               this.item_count--;

                                                               this.cache_valid = false;

                                               }

                               }

                };

                ObjectSet_.prototype.clear = function (/*wipe_*/)

                {

                               if (this.isEmpty())

                                               return;

                               if (supports_set)

                               {

                                               this.s["clear"]();                                   // best!

                               }

                               else

                               {

                                                               this.items = null;                  // creates garbage; will lazy allocate on next add()

                                               this.item_count = 0;

                               }

                               cr.clearArray(this.values_cache);

                               this.cache_valid = true;

                };

                ObjectSet_.prototype.isEmpty = function ()

                {

                               return this.count() === 0;

                };

                ObjectSet_.prototype.count = function ()

                {

                               if (supports_set)

                                               return this.s["size"];

                               else

                                               return this.item_count;

                };

                var current_arr = null;

                var current_index = 0;

                function set_append_to_arr(x)

                {

                               current_arr[current_index++] = x;

                };

                ObjectSet_.prototype.update_cache = function ()

                {

                               if (this.cache_valid)

                                               return;

                               if (supports_set)

                               {

                                               cr.clearArray(this.values_cache);

                                               current_arr = this.values_cache;

                                               current_index = 0;

                                               this.s["forEach"](set_append_to_arr);

;

                                               current_arr = null;

                                               current_index = 0;

                               }

                               else

                               {

                                               var values_cache = this.values_cache;

                                               cr.clearArray(values_cache);

                                               var p, n = 0, items = this.items;

                                               if (items)

                                               {

                                                               for (p in items)

                                                               {

                                                                              if (items.hasOwnProperty(p))

                                                                                              values_cache[n++] = items[p];

                                                               }

                                               }

;

                               }

                               this.cache_valid = true;

                };

                ObjectSet_.prototype.valuesRef = function ()

                {

                               this.update_cache();

                               return this.values_cache;

                };

                cr.ObjectSet = ObjectSet_;

                var tmpSet = new cr.ObjectSet();

                cr.removeArrayDuplicates = function (arr)

                {

                               var i, len;

                               for (i = 0, len = arr.length; i < len; ++i)

                               {

                                               tmpSet.add(arr[i]);

                               }

                               cr.shallowAssignArray(arr, tmpSet.valuesRef());

                               tmpSet.clear();

                };

                cr.arrayRemoveAllFromObjectSet = function (arr, remset)

                {

                               if (supports_set)

                                               cr.arrayRemoveAll_set(arr, remset.s);

                               else

                                               cr.arrayRemoveAll_arr(arr, remset.valuesRef());

                };

                cr.arrayRemoveAll_set = function (arr, s)

                {

                               var i, j, len, item;

                               for (i = 0, j = 0, len = arr.length; i < len; ++i)

                               {

                                               item = arr[i];

                                               if (!s["has"](item))                                                                             // not an item to remove

                                                               arr[j++] = item;                                                                   // keep it

                               }

                               cr.truncateArray(arr, j);

                };

                cr.arrayRemoveAll_arr = function (arr, rem)

                {

                               var i, j, len, item;

                               for (i = 0, j = 0, len = arr.length; i < len; ++i)

                               {

                                               item = arr[i];

                                               if (cr.fastIndexOf(rem, item) === -1)             // not an item to remove

                                                               arr[j++] = item;                                                                   // keep it

                               }

                               cr.truncateArray(arr, j);

                };

                function KahanAdder_()

                {

                               this.c = 0;.y = 0;.t = 0;.sum = 0;

                               cr.seal(this);

                };

                KahanAdder_.prototype.add = function (v)

                {

                               this.y = v - this.c;

                 this.t = this.sum + this.y;

                 this.c = (this.t - this.sum) - this.y;

                 this.sum = this.t;

                };_.prototype.reset = function ()

{.c = 0;.y = 0;.t = 0;.sum = 0;

};

                cr.KahanAdder = KahanAdder_;

                cr.regexp_escape = function(text)

                {

                               return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");

                };

                function CollisionPoly_(pts_array_)

                {

                               this.pts_cache = [];

                               this.bboxLeft = 0;

                               this.bboxTop = 0;

                               this.bboxRight = 0;

                               this.bboxBottom = 0;

                               this.convexpolys = null;                    // for physics behavior to cache separated polys

                               this.set_pts(pts_array_);

                               cr.seal(this);

                };

                CollisionPoly_.prototype.set_pts = function(pts_array_)

                {

                               this.pts_array = pts_array_;

                               this.pts_count = pts_array_.length / 2;                                         // x, y, x, y... in array

                               this.pts_cache.length = pts_array_.length;

                               this.cache_width = -1;

                               this.cache_height = -1;

                               this.cache_angle = 0;

                };

                CollisionPoly_.prototype.is_empty = function()

                {

                               return!this.pts_array.length;

                };

                CollisionPoly_.prototype.update_bbox = function ()

                {

                               var myptscache = this.pts_cache;

                               var bboxLeft_ = myptscache[0];

                               var bboxRight_ = bboxLeft_;

                               var bboxTop_ = myptscache[1];

                               var bboxBottom_ = bboxTop_;

                               var x, y, i = 1, i2, len = this.pts_count;

                               for (; i < len; ++i)

                               {

                                               i2 = i*2;

                                               x = myptscache[i2];

                                               y = myptscache[i2+1];

                                               if (x < bboxLeft_)

                                                               bboxLeft_ = x;

                                               if (x > bboxRight_)

                                                               bboxRight_ = x;

                                               if (y < bboxTop_)

                                                               bboxTop_ = y;

                                               if (y > bboxBottom_)

                                                               bboxBottom_ = y;

                               }

                               this.bboxLeft = bboxLeft_;

                               this.bboxRight = bboxRight_;

                               this.bboxTop = bboxTop_;

                               this.bboxBottom = bboxBottom_;

                };

                CollisionPoly_.prototype.set_from_rect = function(rc, offx, offy)

                {

                               this.pts_cache.length = 8;

                               this.pts_count = 4;

                               var myptscache = this.pts_cache;

                               myptscache[0] = rc.left - offx;

                               myptscache[1] = rc.top - offy;

                               myptscache[2] = rc.right - offx;

                               myptscache[3] = rc.top - offy;

                               myptscache[4] = rc.right - offx;

                               myptscache[5] = rc.bottom - offy;

                               myptscache[6] = rc.left - offx;

                               myptscache[7] = rc.bottom - offy;

                               this.cache_width = rc.right - rc.left;

                               this.cache_height = rc.bottom - rc.top;

                               this.update_bbox();

                };

                CollisionPoly_.prototype.set_from_quad = function(q, offx, offy, w, h)

                {

                               this.pts_cache.length = 8;

                               this.pts_count = 4;

                               var myptscache = this.pts_cache;

                               myptscache[0] = q.tlx - offx;

                               myptscache[1] = q.tly - offy;

                               myptscache[2] = q.trx - offx;

                               myptscache[3] = q.try_ - offy;

                               myptscache[4] = q.brx - offx;

                               myptscache[5] = q.bry - offy;

                               myptscache[6] = q.blx - offx;

                               myptscache[7] = q.bly - offy;

                               this.cache_width = w;

                               this.cache_height = h;

                               this.update_bbox();

                };

                CollisionPoly_.prototype.set_from_poly = function (r)

                {

                               this.pts_count = r.pts_count;

                               cr.shallowAssignArray(this.pts_cache, r.pts_cache);

                               this.bboxLeft = r.bboxLeft;

                               this.bboxTop - r.bboxTop;

                               this.bboxBottom = r.bboxBottom;

                };

                CollisionPoly_.prototype.cache_poly = function(w, h, a)

                {

                               if (this.cache_width === w && this.cache_height === h && this.cache_angle === a)

                                               return;                   // cache up-to-date

                               this.cache_width = w;

                               this.cache_height = h;

                               this.cache_angle = a;

                               var i, i2, i21, len, x, y;

                               var sina = 0;

                               var cosa = 1;

                               var myptsarray = this.pts_array;

                               var myptscache = this.pts_cache;

                               if (a!== 0)

                               {

                                               sina = Math.sin(a);

                                               cosa = Math.cos(a);

                               }

                               for (i = 0, len = this.pts_count; i < len; i++)

                               {

                                               i2 = i*2;

                                               i21 = i2+1;

                                               x = myptsarray[i2] * w;

                                               y = myptsarray[i21] * h;

                                               myptscache[i2] = (x * cosa) - (y * sina);

                                               myptscache[i21] = (y * cosa) + (x * sina);

                               }

                               this.update_bbox();

                };

                CollisionPoly_.prototype.contains_pt = function (a2x, a2y)

                {

                                var myptscache = this.pts_cache;

                               if (a2x === myptscache[0] && a2y === myptscache[1])

                                               return true;

                               var i, i2, imod, len = this.pts_count;

                               var a1x = this.bboxLeft - 110;

                               var a1y = this.bboxTop - 101;

                               var a3x = this.bboxRight + 131

                               var a3y = this.bboxBottom + 120;

                               var b1x, b1y, b2x, b2y;

                               var count1 = 0, count2 = 0;

                               for (i = 0; i < len; i++)

                               {

                                               i2 = i*2;

                                               imod = ((i+1)%len)*2;

                                               b1x = myptscache[i2];

                                               b1y = myptscache[i2+1];

                                               b2x = myptscache[imod];

                                               b2y = myptscache[imod+1];

                                               if (cr.segments_intersect(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y))

                                                               count1++;

                                               if (cr.segments_intersect(a3x, a3y, a2x, a2y, b1x, b1y, b2x, b2y))

                                                               count2++;

                               }

                               return (count1 % 2 === 1) || (count2 % 2 === 1);

                };

                CollisionPoly_.prototype.intersects_poly = function (rhs, offx, offy)

                {

                               var rhspts = rhs.pts_cache;

                               var mypts = this.pts_cache;

                               if (this.contains_pt(rhspts[0] + offx, rhspts[1] + offy))

                                               return true;

                               if (rhs.contains_pt(mypts[0] - offx, mypts[1] - offy))

                                               return true;

                               var i, i2, imod, leni, j, j2, jmod, lenj;

                               var a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y;

                               for (i = 0, leni = this.pts_count; i < leni; i++)

                               {

                                               i2 = i*2;

                                               imod = ((i+1)%leni)*2;

                                               a1x = mypts[i2];

                                               a1y = mypts[i2+1];

                                               a2x = mypts[imod];

                                               a2y = mypts[imod+1];

                                               for (j = 0, lenj = rhs.pts_count; j < lenj; j++)

                                               {

                                                               j2 = j*2;

                                                               jmod = ((j+1)%lenj)*2;

                                                               b1x = rhspts[j2] + offx;

                                                               b1y = rhspts[j2+1] + offy;

                                                               b2x = rhspts[jmod] + offx;

                                                               b2y = rhspts[jmod+1] + offy;

                                                               if (cr.segments_intersect(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y))

                                                                              return true;

                                               }

                               }

                               return false;

                };

                CollisionPoly_.prototype.intersects_segment = function (offx, offy, x1, y1, x2, y2)

                {

                               var mypts = this.pts_cache;

                               if (this.contains_pt(x1 - offx, y1 - offy))

                                               return true;

                               var i, leni, i2, imod;

                               var a1x, a1y, a2x, a2y;

                               for (i = 0, leni = this.pts_count; i < leni; i++)

                               {

                                               i2 = i*2;

                                               imod = ((i+1)%leni)*2;

                                               a1x = mypts[i2] + offx;

                                               a1y = mypts[i2+1] + offy;

                                               a2x = mypts[imod] + offx;

                                               a2y = mypts[imod+1] + offy;

                                               if (cr.segments_intersect(x1, y1, x2, y2, a1x, a1y, a2x, a2y))

                                                               return true;

                               }

                               return false;

                };

                CollisionPoly_.prototype.mirror = function (px)

                {

                               var i, leni, i2;

                                for (i = 0, leni = this.pts_count; i < leni; ++i)

                               {

                                               i2 = i*2;

                                               this.pts_cache[i2] = px * 2 - this.pts_cache[i2];

                               }

                };

                CollisionPoly_.prototype.flip = function (py)

                {

                               var i, leni, i21;

                               for (i = 0, leni = this.pts_count; i < leni; ++i)

                               {

                                               i21 = i*2+1;

                                               this.pts_cache[i21] = py * 2 - this.pts_cache[i21];

                               }

                };

                CollisionPoly_.prototype.diag = function ()

                {

                               var i, leni, i2, i21, temp;

                               for (i = 0, leni = this.pts_count; i < leni; ++i)

                               {

                                               i2 = i*2;

                                               i21 = i2+1;

                                               temp = this.pts_cache[i2];

                                               this.pts_cache[i2] = this.pts_cache[i21];

                                               this.pts_cache[i21] = temp;

                               }

                };

                cr.CollisionPoly = CollisionPoly_;

                function SparseGrid_(cellwidth_, cellheight_)

                {

                               this.cellwidth = cellwidth_;

                               this.cellheight = cellheight_;

                               this.cells = {};

                };

                SparseGrid_.prototype.totalCellCount = 0;

                SparseGrid_.prototype.getCell = function (x_, y_, create_if_missing)

                {

                               var ret;

                               var col = this.cells[x_];

                               if (!col)

                               {

                                               if (create_if_missing)

                                               {

                                                               ret = allocGridCell(this, x_, y_);

                                                               this.cells[x_] = {};

                                                               this.cells[x_][y_] = ret;

                                                               return ret;

                                               }

                                               else

                                                               return null;

                               }

                               ret = col[y_];

                               if (ret)

                                               return ret;

                               else if (create_if_missing)

                               {

                                               ret = allocGridCell(this, x_, y_);

                                               this.cells[x_][y_] = ret;

                                               return ret;

                               }

                               else

                                               return null;

                };

                SparseGrid_.prototype.XToCell = function (x_)

                {

                               return cr.floor(x_ / this.cellwidth);

                };

                SparseGrid_.prototype.YToCell = function (y_)

                {

                               return cr.floor(y_ / this.cellheight);

                };

                SparseGrid_.prototype.update = function (inst, oldrange, newrange)

                {

                               var x, lenx, y, leny, cell;

                               if (oldrange)

                               {

                                               for (x = oldrange.left, lenx = oldrange.right; x <= lenx; ++x)

                                               {

                                                               for (y = oldrange.top, leny = oldrange.bottom; y <= leny; ++y)

                                                               {

                                                                              if (newrange && newrange.contains_pt(x, y))

                                                                                              continue;              // is still in this cell

                                                                              cell = this.getCell(x, y, false);           // don't create if missing

                                                                              if (!cell)

                                                                                              continue;              // cell does not exist yet

                                                                              cell.remove(inst);

                                                                              if (cell.isEmpty())

                                                                              {

                                                                                              freeGridCell(cell);

                                                                                              this.cells[x][y] = null;

                                                                              }

                                                               }

                                               }

                               }

                               if (newrange)

                               {

                                               for (x = newrange.left, lenx = newrange.right; x <= lenx; ++x)

                                               {

                                                               for (y = newrange.top, leny = newrange.bottom; y <= leny; ++y)

                                                               {

                                                                              if (oldrange && oldrange.contains_pt(x, y))

                                                                                              continue;              // is still in this cell

                                                                              this.getCell(x, y, true).insert(inst);

                                                               }

                                               }

                               }

                };

                SparseGrid_.prototype.queryRange = function (rc, result)

                {

                               var x, lenx, ystart, y, leny, cell;

                               x = this.XToCell(rc.left);

                               ystart = this.YToCell(rc.top);

                               lenx = this.XToCell(rc.right);

                               leny = this.YToCell(rc.bottom);

                               for (; x <= lenx; ++x)

                               {

                                               for (y = ystart; y <= leny; ++y)

                                               {

                                                               cell = this.getCell(x, y, false);

                                                               if (!cell)

                                                                              continue;

                                                               cell.dump(result);

                                               }

                               }

                };

                cr.SparseGrid = SparseGrid_;

                function RenderGrid_(cellwidth_, cellheight_)

                {

                               this.cellwidth = cellwidth_;

                               this.cellheight = cellheight_;

                               this.cells = {};

                };

                RenderGrid_.prototype.totalCellCount = 0;

                RenderGrid_.prototype.getCell = function (x_, y_, create_if_missing)

                {

                               var ret;

                               var col = this.cells[x_];

                               if (!col)

                               {

                                               if (create_if_missing)

                                               {

                                                               ret = allocRenderCell(this, x_, y_);

                                                               this.cells[x_] = {};

                                                               this.cells[x_][y_] = ret;

                                                               return ret;

                                               }

                                               else

                                                               return null;

                               }

                               ret = col[y_];

                               if (ret)

                                               return ret;

                               else if (create_if_missing)

                               {

                                               ret = allocRenderCell(this, x_, y_);

                                               this.cells[x_][y_] = ret;

                                               return ret;

                               }

                               else

                                               return null;

                };

                RenderGrid_.prototype.XToCell = function (x_)

                {

                               return cr.floor(x_ / this.cellwidth);

                };

                RenderGrid_.prototype.YToCell = function (y_)

                {

                               return cr.floor(y_ / this.cellheight);

                };

                RenderGrid_.prototype.update = function (inst, oldrange, newrange)

                {

                               var x, lenx, y, leny, cell;

                               if (oldrange)

                               {

                                               for (x = oldrange.left, lenx = oldrange.right; x <= lenx; ++x)

                                               {

                                                               for (y = oldrange.top, leny = oldrange.bottom; y <= leny; ++y)

                                                               {

                                                                              if (newrange && newrange.contains_pt(x, y))

                                                                                              continue;              // is still in this cell

                                                                              cell = this.getCell(x, y, false);           // don't create if missing

                                                                              if (!cell)

                                                                                              continue;              // cell does not exist yet

                                                                              cell.remove(inst);

                                                                              if (cell.isEmpty())

                                                                              {

                                                                                              freeRenderCell(cell);

                                                                                              this.cells[x][y] = null;

                                                                              }

                                                               }

                                               }

                               }

                               if (newrange)

                               {

                                               for (x = newrange.left, lenx = newrange.right; x <= lenx; ++x)

                                               {

                                                               for (y = newrange.top, leny = newrange.bottom; y <= leny; ++y)

                                                               {

                                                                              if (oldrange && oldrange.contains_pt(x, y))

                                                                                              continue;              // is still in this cell

                                                                              this.getCell(x, y, true).insert(inst);

                                                               }

                                               }

                               }

                };

                RenderGrid_.prototype.queryRange = function (left, top, right, bottom, result)

                {

                               var x, lenx, ystart, y, leny, cell;

                               x = this.XToCell(left);

                               ystart = this.YToCell(top);

                               lenx = this.XToCell(right);

                               leny = this.YToCell(bottom);

                               for (; x <= lenx; ++x)

                               {

                                               for (y = ystart; y <= leny; ++y)

                                               {

                                                               cell = this.getCell(x, y, false);

                                                               if (!cell)

                                                                              continue;

                                                               cell.dump(result);

                                               }

                               }

                };

                RenderGrid_.prototype.markRangeChanged = function (rc)

                {

                               var x, lenx, ystart, y, leny, cell;

                               x = rc.left;

                               ystart = rc.top;

                               lenx = rc.right;

                               leny = rc.bottom;

                               for (; x <= lenx; ++x)

                               {

                                               for (y = ystart; y <= leny; ++y)

                                               {

                                                               cell = this.getCell(x, y, false);

                                                               if (!cell)

                                                                              continue;

                                                               cell.is_sorted = false;

                               }

                };

                cr.RenderGrid = RenderGrid_;

                var gridcellcache = [];

                function allocGridCell(grid_, x_, y_)

                {

                               var ret;

                               SparseGrid_.prototype.totalCellCount++;

                               if (gridcellcache.length)

                               {

                                               ret = gridcellcache.pop();

                                               ret.grid = grid_;

                                               ret.x = x_;

                                               ret.y = y_;

                                               return ret;

                               }

                               else

                                               return new cr.GridCell(grid_, x_, y_);

                };

                function freeGridCell(c)

                {

                               SparseGrid_.prototype.totalCellCount--;

                               c.objects.clear();

                               if (gridcellcache.length < 1000)

                                               gridcellcache.push(c);

                };

                function GridCell_(grid_, x_, y_)

                {

                               this.grid = grid_;

                               this.x = x_;

                               this.y = y_;

                               this.objects = new cr.ObjectSet();

                };

                GridCell_.prototype.isEmpty = function ()

                {

                               return this.objects.isEmpty();

                };

                GridCell_.prototype.insert = function (inst)

                {

                               this.objects.add(inst);

                };

                GridCell_.prototype.remove = function (inst)

                {

                               this.objects.remove(inst);

                };

                GridCell_.prototype.dump = function (result)

                {

                               cr.appendArray(result, this.objects.valuesRef());

                };

                cr.GridCell = GridCell_;

                var rendercellcache = [];

                function allocRenderCell(grid_, x_, y_)

                {

                               var ret;

                               RenderGrid_.prototype.totalCellCount++;

                               if (rendercellcache.length)

                               {

                                               ret = rendercellcache.pop();

                                               ret.grid = grid_;

                                               ret.x = x_;

                                               ret.y = y_;

                                               return ret;

                               }

                               else

                                               return new cr.RenderCell(grid_, x_, y_);

                };

                function freeRenderCell(c)

                {

                               RenderGrid_.prototype.totalCellCount--;

                               c.reset();

                               if (rendercellcache.length < 1000)

                                               rendercellcache.push(c);

                };

                function RenderCell_(grid_, x_, y_)

                {

                               this.grid = grid_;

                               this.x = x_;

                               this.y = y_;

                               this.objects = [];                  // array which needs to be sorted by Z order

                               this.is_sorted = true;           // whether array is in correct sort order or not

                               this.pending_removal = new cr.ObjectSet();

                               this.any_pending_removal = false;

                };

                RenderCell_.prototype.isEmpty = function ()

                {

                               if (!this.objects.length)

                               {

;

;

                                               return true;

                               }

                               if (this.objects.length > this.pending_removal.count())

                                               return false;

;

                               this.flush_pending();                          // takes fast path and just resets state

                               return true;

                };

                RenderCell_.prototype.insert = function (inst)

                {

                               if (this.pending_removal.contains(inst))

                               {

                                               this.pending_removal.remove(inst);

                                               if (this.pending_removal.isEmpty())

                                                               this.any_pending_removal = false;

                                               return;

                               }

                               if (this.objects.length)

                               {

                                               var top = this.objects[this.objects.length - 1];

                                               if (top.get_zindex() > inst.get_zindex())

                                                               this.is_sorted = false;                         // 'inst' should be somewhere beneath 'top'

                                               this.objects.push(inst);

                               }

                               else

                               {

                                               this.objects.push(inst);

                                               this.is_sorted = true;

                               }

;

                };

                RenderCell_.prototype.remove = function (inst)

                {

                               this.pending_removal.add(inst);

                               this.any_pending_removal = true;

                               if (this.pending_removal.count() >= 30)

                                               this.flush_pending();

                };

                RenderCell_.prototype.flush_pending = function ()

                {

;

                               if (!this.any_pending_removal)

                                               return;                   // not changed

                               if (this.pending_removal.count() === this.objects.length)

                               {

                                               this.reset();

                                               return;

                               }

                               cr.arrayRemoveAllFromObjectSet(this.objects, this.pending_removal);

                               this.pending_removal.clear();

                               this.any_pending_removal = false;

                };

                function sortByInstanceZIndex(a, b)

                {

                               return a.zindex - b.zindex;

                };

                RenderCell_.prototype.ensure_sorted = function ()

                {

                               if (this.is_sorted)

                                               return;                   // already sorted

                               this.objects.sort(sortByInstanceZIndex);

                               this.is_sorted = true;

                };

                RenderCell_.prototype.reset = function ()

                {

                               cr.clearArray(this.objects);

                               this.is_sorted = true;

                               this.pending_removal.clear();

                               this.any_pending_removal = false;

                };

                RenderCell_.prototype.dump = function (result)

                {

                               this.flush_pending();

                               this.ensure_sorted();

                               if (this.objects.length)

                                               result.push(this.objects);

                };

                cr.RenderCell = RenderCell_;

                var fxNames = [ "lighter",

                                                                              "xor",

                                                                              "copy",

                                                                              "destination-over",

                                                                              "source-in",

                                                                              "destination-in",

                                                                              "source-out",

                                                                              "destination-out",

                                                                              "source-atop",

                                                                              "destination-atop"];

                cr.effectToCompositeOp = function(effect)

                {

                               if (effect <= 0 || effect >= 11)

                                               return "source-over";

                               return fxNames[effect - 1];              // not including "none" so offset by 1

                };

                cr.setGLBlend = function(this_, effect, gl)

                {

                               if (!gl)

                                               return;

                               this_.srcBlend = gl.ONE;

                               this_.destBlend = gl.ONE_MINUS_SRC_ALPHA;

                               switch (effect) {

                               case 1:                   // lighter (additive)

                                               this_.srcBlend = gl.ONE;

                                               this_.destBlend = gl.ONE;

                                               break;

                               case 2:                   // xor

                                               break;    // todo

                               case 3:                   // copy

                                               this_.srcBlend = gl.ONE;

                                               this_.destBlend = gl.ZERO;

                                               break;

                               case 4:                   // destination-over

                                               this_.srcBlend = gl.ONE_MINUS_DST_ALPHA;

                                               this_.destBlend = gl.ONE;

                                               break;

                               case 5:                   // source-in

                                               this_.srcBlend = gl.DST_ALPHA;

                                               this_.destBlend = gl.ZERO;

                                               break;

                               case 6:                   // destination-in

                                               this_.srcBlend = gl.ZERO;

                                               this_.destBlend = gl.SRC_ALPHA;

                                               break;

                               case 7:                   // source-out

                                               this_.srcBlend = gl.ONE_MINUS_DST_ALPHA;

                                               this_.destBlend = gl.ZERO;

                                               break;

                               case 8:                   // destination-out

                                               this_.srcBlend = gl.ZERO;

                                               this_.destBlend = gl.ONE_MINUS_SRC_ALPHA;

                                               break;

                               case 9:                   // source-atop

                                               this_.srcBlend = gl.DST_ALPHA;

                                               this_.destBlend = gl.ONE_MINUS_SRC_ALPHA;

                                               break;

                               case 10: // destination-atop

                                               this_.srcBlend = gl.ONE_MINUS_DST_ALPHA;

                                               this_.destBlend = gl.SRC_ALPHA;

                                               break;

                               }

                };

                cr.round6dp = function (x)

                {

                               return Math.round(x * 1000000) / 1000000;

                };

                /*

                var localeCompare_options = {

                               "usage": "search",

                               "sensitivity": "accent"

                };

                var has_localeCompare =!!"a".localeCompare;

                var localeCompare_works1 = (has_localeCompare && "a".localeCompare("A", undefined, localeCompare_options) === 0);

                var localeCompare_works2 = (has_localeCompare && "a".localeCompare("á", undefined, localeCompare_options)!== 0);

                var supports_localeCompare = (has_localeCompare && localeCompare_works1 && localeCompare_works2);

                */

                cr.equals_nocase = function (a, b)

                {

                               if (typeof a!== "string" || typeof b!== "string")

                                               return false;

                               if (a.length!== b.length)

                                               return false;

                               if (a === b)

                                               return true;

                               /*

                               if (supports_localeCompare)

                               {

                                               return (a.localeCompare(b, undefined, localeCompare_options) === 0);

                               }

                               else

                               {

                               */

                                               return a.toLowerCase() === b.toLowerCase();

                };

                cr.isCanvasInputEvent = function (e)

                {

                               var target = e.target;

                               if (!target)

                                               return true;

                               if (target === document || target === window)

                                               return true;

                               if (document && document.body && target === document.body)

                                               return true;

                               if (cr.equals_nocase(target.tagName, "canvas"))

                                               return true;

                               return false;

                };

}());MatrixArray=typeof Float32Array!=="undefined"?Float32Array:Array,glMatrixArrayType=MatrixArray,vec3={},mat3={},mat4={},quat4={};vec3.create=function(a){var b=new MatrixArray(3);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2]);return b};vec3.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];return b};vec3.add=function(a,b,c){if(!c||a===c)return a[0]+=b[0],a[1]+=b[1],a[2]+=b[2],a;c[0]=a[0]+b[0];c[1]=a[1]+b[1];c[2]=a[2]+b[2];return c};.subtract=function(a,b,c){if(!c||a===c)return a[0]-=b[0],a[1]-=b[1],a[2]-=b[2],a;c[0]=a[0]-b[0];c[1]=a[1]-b[1];c[2]=a[2]-b[2];return c};vec3.negate=function(a,b){b||(b=a);b[0]=-a[0];b[1]=-a[1];b[2]=-a[2];return b};vec3.scale=function(a,b,c){if(!c||a===c)return a[0]*=b,a[1]*=b,a[2]*=b,a;c[0]=a[0]*b;c[1]=a[1]*b;c[2]=a[2]*b;return c};.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=Math.sqrt(c*c+d*d+e*e);if(g){if(g===1)return b[0]=c,b[1]=d,b[2]=e,b}else return b[0]=0,b[1]=0,b[2]=0,b;g=1/g;b[0]=c*g;b[1]=d*g;b[2]=e*g;return b};vec3.cross=function(a,b,c){c||(c=a);var d=a[0],e=a[1],a=a[2],g=b[0],f=b[1],b=b[2];c[0]=e*b-a*f;c[1]=a*g-d*b;c[2]=d*f-e*g;return c};vec3.length=function(a){var b=a[0],c=a[1],a=a[2];return Math.sqrt(b*b+c*c+a*a)};vec3.dot=function(a,b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]};.direction=function(a,b,c){c||(c=a);var d=a[0]-b[0],e=a[1]-b[1],a=a[2]-b[2],b=Math.sqrt(d*d+e*e+a*a);if(!b)return c[0]=0,c[1]=0,c[2]=0,c;b=1/b;c[0]=d*b;c[1]=e*b;c[2]=a*b;return c};vec3.lerp=function(a,b,c,d){d||(d=a);d[0]=a[0]+c*(b[0]-a[0]);d[1]=a[1]+c*(b[1]-a[1]);d[2]=a[2]+c*(b[2]-a[2]);return d};vec3.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+"]"};.create=function(a){var b=new MatrixArray(9);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8]);return b};mat3.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];return b};mat3.identity=function(a){a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=1;a[5]=0;a[6]=0;a[7]=0;a[8]=1;return a};.transpose=function(a,b){if(!b||a===b){var c=a[1],d=a[2],e=a[5];a[1]=a[3];a[2]=a[6];a[3]=c;a[5]=a[7];a[6]=d;a[7]=e;return a}b[0]=a[0];b[1]=a[3];b[2]=a[6];b[3]=a[1];b[4]=a[4];b[5]=a[7];b[6]=a[2];b[7]=a[5];b[8]=a[8];return b};mat3.toMat4=function(a,b){b||(b=mat4.create());b[15]=1;b[14]=0;b[13]=0;b[12]=0;b[11]=0;b[10]=a[8];b[9]=a[7];b[8]=a[6];b[7]=0;b[6]=a[5];b[5]=a[4];b[4]=a[3];b[3]=0;b[2]=a[2];b[1]=a[1];b[0]=a[0];return b};.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+"]"};mat4.create=function(a){var b=new MatrixArray(16);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3],b[4]=a[4],b[5]=a[5],b[6]=a[6],b[7]=a[7],b[8]=a[8],b[9]=a[9],b[10]=a[10],b[11]=a[11],b[12]=a[12],b[13]=a[13],b[14]=a[14],b[15]=a[15]);return b};.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=a[12];b[13]=a[13];b[14]=a[14];b[15]=a[15];return b};mat4.identity=function(a){a[0]=1;a[1]=0;a[2]=0;a[3]=0;a[4]=0;a[5]=1;a[6]=0;a[7]=0;a[8]=0;a[9]=0;a[10]=1;a[11]=0;a[12]=0;a[13]=0;a[14]=0;a[15]=1;return a};.transpose=function(a,b){if(!b||a===b){var c=a[1],d=a[2],e=a[3],g=a[6],f=a[7],h=a[11];a[1]=a[4];a[2]=a[8];a[3]=a[12];a[4]=c;a[6]=a[9];a[7]=a[13];a[8]=d;a[9]=g;a[11]=a[14];a[12]=e;a[13]=f;a[14]=h;return a}b[0]=a[0];b[1]=a[4];b[2]=a[8];b[3]=a[12];b[4]=a[1];b[5]=a[5];b[6]=a[9];b[7]=a[13];b[8]=a[2];b[9]=a[6];b[10]=a[10];b[11]=a[14];b[12]=a[3];b[13]=a[7];b[14]=a[11];b[15]=a[15];return b};.determinant=function(a){var b=a[0],c=a[1],d=a[2],e=a[3],g=a[4],f=a[5],h=a[6],i=a[7],j=a[8],k=a[9],l=a[10],n=a[11],o=a[12],m=a[13],p=a[14],a=a[15];return o*k*h*e-j*m*h*e-o*f*l*e+g*m*l*e+j*f*p*e-g*k*p*e-o*k*d*i+j*m*d*i+o*c*l*i-b*m*l*i-j*c*p*i+b*k*p*i+o*f*d*n-g*m*d*n-o*c*h*n+b*m*h*n+g*c*p*n-b*f*p*n-j*f*d*a+g*k*d*a+j*c*h*a-b*k*h*a-g*c*l*a+b*f*l*a};.inverse=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=a[3],f=a[4],h=a[5],i=a[6],j=a[7],k=a[8],l=a[9],n=a[10],o=a[11],m=a[12],p=a[13],r=a[14],s=a[15],A=c*h-d*f,B=c*i-e*f,t=c*j-g*f,u=d*i-e*h,v=d*j-g*h,w=e*j-g*i,x=k*p-l*m,y=k*r-n*m,z=k*s-o*m,C=l*r-n*p,D=l*s-o*p,E=n*s-o*r,q=1/(A*E-B*D+t*C+u*z-v*y+w*x);b[0]=(h*E-i*D+j*C)*q;b[1]=(-d*E+e*D-g*C)*q;b[2]=(p*w-r*v+s*u)*q;b[3]=(-l*w+n*v-o*u)*q;b[4]=(-f*E+i*z-j*y)*q;b[5]=(c*E-e*z+g*y)*q;b[6]=(-m*w+r*t-s*B)*q;b[7]=(k*w-n*t+o*B)*q;b[8]=(f*D-h*z+j*x)*q;[9]=(-c*D+d*z-g*x)*q;b[10]=(m*v-p*t+s*A)*q;b[11]=(-k*v+l*t-o*A)*q;b[12]=(-f*C+h*y-i*x)*q;b[13]=(c*C-d*y+e*x)*q;b[14]=(-m*u+p*B-r*A)*q;b[15]=(k*u-l*B+n*A)*q;return b};mat4.toRotationMat=function(a,b){b||(b=mat4.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];b[4]=a[4];b[5]=a[5];b[6]=a[6];b[7]=a[7];b[8]=a[8];b[9]=a[9];b[10]=a[10];b[11]=a[11];b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b};.toMat3=function(a,b){b||(b=mat3.create());b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[4];b[4]=a[5];b[5]=a[6];b[6]=a[8];b[7]=a[9];b[8]=a[10];return b};mat4.toInverseMat3=function(a,b){var c=a[0],d=a[1],e=a[2],g=a[4],f=a[5],h=a[6],i=a[8],j=a[9],k=a[10],l=k*f-h*j,n=-k*g+h*i,o=j*g-f*i,m=c*l+d*n+e*o;if(!m)return null;m=1/m;b||(b=mat3.create());b[0]=l*m;b[1]=(-k*d+e*j)*m;b[2]=(h*d-e*f)*m;b[3]=n*m;b[4]=(k*c-e*i)*m;b[5]=(-h*c+e*g)*m;b[6]=o*m;b[7]=(-j*c+d*i)*m;b[8]=(f*c-d*g)*m;return b};.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],g=a[2],f=a[3],h=a[4],i=a[5],j=a[6],k=a[7],l=a[8],n=a[9],o=a[10],m=a[11],p=a[12],r=a[13],s=a[14],a=a[15],A=b[0],B=b[1],t=b[2],u=b[3],v=b[4],w=b[5],x=b[6],y=b[7],z=b[8],C=b[9],D=b[10],E=b[11],q=b[12],F=b[13],G=b[14],b=b[15];c[0]=A*d+B*h+t*l+u*p;c[1]=A*e+B*i+t*n+u*r;c[2]=A*g+B*j+t*o+u*s;c[3]=A*f+B*k+t*m+u*a;c[4]=v*d+w*h+x*l+y*p;c[5]=v*e+w*i+x*n+y*r;c[6]=v*g+w*j+x*o+y*s;c[7]=v*f+w*k+x*m+y*a;c[8]=z*d+C*h+D*l+E*p;c[9]=z*e+C*i+D*n+E*r;c[10]=z*g+C*+D*o+E*s;c[11]=z*f+C*k+D*m+E*a;c[12]=q*d+F*h+G*l+b*p;c[13]=q*e+F*i+G*n+b*r;c[14]=q*g+F*j+G*o+b*s;c[15]=q*f+F*k+G*m+b*a;return c};mat4.multiplyVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],b=b[2];c[0]=a[0]*d+a[4]*e+a[8]*b+a[12];c[1]=a[1]*d+a[5]*e+a[9]*b+a[13];c[2]=a[2]*d+a[6]*e+a[10]*b+a[14];return c};.multiplyVec4=function(a,b,c){c||(c=b);var d=b[0],e=b[1],g=b[2],b=b[3];c[0]=a[0]*d+a[4]*e+a[8]*g+a[12]*b;c[1]=a[1]*d+a[5]*e+a[9]*g+a[13]*b;c[2]=a[2]*d+a[6]*e+a[10]*g+a[14]*b;c[3]=a[3]*d+a[7]*e+a[11]*g+a[15]*b;return c};.translate=function(a,b,c){var d=b[0],e=b[1],b=b[2],g,f,h,i,j,k,l,n,o,m,p,r;if(!c||a===c)return a[12]=a[0]*d+a[4]*e+a[8]*b+a[12],a[13]=a[1]*d+a[5]*e+a[9]*b+a[13],a[14]=a[2]*d+a[6]*e+a[10]*b+a[14],a[15]=a[3]*d+a[7]*e+a[11]*b+a[15],a;g=a[0];f=a[1];h=a[2];i=a[3];j=a[4];k=a[5];l=a[6];n=a[7];o=a[8];m=a[9];p=a[10];r=a[11];c[0]=g;c[1]=f;c[2]=h;c[3]=i;c[4]=j;c[5]=k;c[6]=l;c[7]=n;c[8]=o;c[9]=m;c[10]=p;c[11]=r;c[12]=g*d+j*e+o*b+a[12];c[13]=f*d+k*e+m*b+a[13];c[14]=h*d+l*e+p*b+a[14];c[15]=i*d+n*e+r*b+a[15];c};mat4.scale=function(a,b,c){var d=b[0],e=b[1],b=b[2];if(!c||a===c)return a[0]*=d,a[1]*=d,a[2]*=d,a[3]*=d,a[4]*=e,a[5]*=e,a[6]*=e,a[7]*=e,a[8]*=b,a[9]*=b,a[10]*=b,a[11]*=b,a;c[0]=a[0]*d;c[1]=a[1]*d;c[2]=a[2]*d;c[3]=a[3]*d;c[4]=a[4]*e;c[5]=a[5]*e;c[6]=a[6]*e;c[7]=a[7]*e;c[8]=a[8]*b;c[9]=a[9]*b;c[10]=a[10]*b;c[11]=a[11]*b;c[12]=a[12];c[13]=a[13];c[14]=a[14];c[15]=a[15];return c};.rotate=function(a,b,c,d){var e=c[0],g=c[1],c=c[2],f=Math.sqrt(e*e+g*g+c*c),h,i,j,k,l,n,o,m,p,r,s,A,B,t,u,v,w,x,y,z;if(!f)return null;f!==1&&(f=1/f,e*=f,g*=f,c*=f);h=Math.sin(b);i=Math.cos(b);j=1-i;b=a[0];f=a[1];k=a[2];l=a[3];n=a[4];o=a[5];m=a[6];p=a[7];r=a[8];s=a[9];A=a[10];B=a[11];t=e*e*j+i;u=g*e*j+c*h;v=c*e*j-g*h;w=e*g*j-c*h;x=g*g*j+i;y=c*g*j+e*h;z=e*c*j+g*h;e=g*c*j-e*h;g=c*c*j+i;d?a!==d&&(d[12]=a[12],d[13]=a[13],d[14]=a[14],d[15]=a[15]):d=a;d[0]=b*t+n*u+r*v;d[1]=f*t+o*u+s*v;d[2]=k*t+m*u+A*;d[3]=l*t+p*u+B*v;d[4]=b*w+n*x+r*y;d[5]=f*w+o*x+s*y;d[6]=k*w+m*x+A*y;d[7]=l*w+p*x+B*y;d[8]=b*z+n*e+r*g;d[9]=f*z+o*e+s*g;d[10]=k*z+m*e+A*g;d[11]=l*z+p*e+B*g;return d};mat4.rotateX=function(a,b,c){var d=Math.sin(b),b=Math.cos(b),e=a[4],g=a[5],f=a[6],h=a[7],i=a[8],j=a[9],k=a[10],l=a[11];c?a!==c&&(c[0]=a[0],c[1]=a[1],c[2]=a[2],c[3]=a[3],c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15]):c=a;c[4]=e*b+i*d;c[5]=g*b+j*d;c[6]=f*b+k*d;c[7]=h*b+l*d;c[8]=e*-d+i*b;c[9]=g*-d+j*b;c[10]=f*-d+k*b;c[11]=h*-d+l*b;return c};.rotateY=function(a,b,c){var d=Math.sin(b),b=Math.cos(b),e=a[0],g=a[1],f=a[2],h=a[3],i=a[8],j=a[9],k=a[10],l=a[11];c?a!==c&&(c[4]=a[4],c[5]=a[5],c[6]=a[6],c[7]=a[7],c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15]):c=a;c[0]=e*b+i*-d;c[1]=g*b+j*-d;c[2]=f*b+k*-d;c[3]=h*b+l*-d;c[8]=e*d+i*b;c[9]=g*d+j*b;c[10]=f*d+k*b;c[11]=h*d+l*b;return c};.rotateZ=function(a,b,c){var d=Math.sin(b),b=Math.cos(b),e=a[0],g=a[1],f=a[2],h=a[3],i=a[4],j=a[5],k=a[6],l=a[7];c?a!==c&&(c[8]=a[8],c[9]=a[9],c[10]=a[10],c[11]=a[11],c[12]=a[12],c[13]=a[13],c[14]=a[14],c[15]=a[15]):c=a;c[0]=e*b+i*d;c[1]=g*b+j*d;c[2]=f*b+k*d;c[3]=h*b+l*d;c[4]=e*-d+i*b;c[5]=g*-d+j*b;c[6]=f*-d+k*b;c[7]=h*-d+l*b;return c};.frustum=function(a,b,c,d,e,g,f){f||(f=mat4.create());var h=b-a,i=d-c,j=g-e;f[0]=e*2/h;f[1]=0;f[2]=0;f[3]=0;f[4]=0;f[5]=e*2/i;f[6]=0;f[7]=0;f[8]=(b+a)/h;f[9]=(d+c)/i;f[10]=-(g+e)/j;f[11]=-1;f[12]=0;f[13]=0;f[14]=-(g*e*2)/j;f[15]=0;return f};mat4.perspective=function(a,b,c,d,e){a=c*Math.tan(a*Math.PI/360);b*=a;return mat4.frustum(-b,b,-a,a,c,d,e)};.ortho=function(a,b,c,d,e,g,f){f||(f=mat4.create());var h=b-a,i=d-c,j=g-e;f[0]=2/h;f[1]=0;f[2]=0;f[3]=0;f[4]=0;f[5]=2/i;f[6]=0;f[7]=0;f[8]=0;f[9]=0;f[10]=-2/j;f[11]=0;f[12]=-(a+b)/h;f[13]=-(d+c)/i;f[14]=-(g+e)/j;f[15]=1;return f};.lookAt=function(a,b,c,d){d||(d=mat4.create());var e,g,f,h,i,j,k,l,n=a[0],o=a[1],a=a[2];g=c[0];f=c[1];e=c[2];c=b[1];j=b[2];if(n===b[0]&&o===c&&a===j)return mat4.identity(d);c=n-b[0];j=o-b[1];k=a-b[2];l=1/Math.sqrt(c*c+j*j+k*k);c*=l;j*=l;k*=l;b=f*k-e*j;e=e*c-g*k;g=g*j-f*c;(l=Math.sqrt(b*b+e*e+g*g))?(l=1/l,b*=l,e*=l,g*=l):g=e=b=0;f=j*g-k*e;h=k*b-c*g;i=c*e-j*b;(l=Math.sqrt(f*f+h*h+i*i))?(l=1/l,f*=l,h*=l,i*=l):i=h=f=0;d[0]=b;d[1]=f;d[2]=c;d[3]=0;d[4]=e;d[5]=h;d[6]=j;d[7]=0;d[8]=g;d[9]=i;d[10]=k;d[11]=

;d[12]=-(b*n+e*o+g*a);d[13]=-(f*n+h*o+i*a);d[14]=-(c*n+j*o+k*a);d[15]=1;return d};mat4.fromRotationTranslation=function(a,b,c){c||(c=mat4.create());var d=a[0],e=a[1],g=a[2],f=a[3],h=d+d,i=e+e,j=g+g,a=d*h,k=d*i;d*=j;var l=e*i;e*=j;g*=j;h*=f;i*=f;f*=j;c[0]=1-(l+g);c[1]=k+f;c[2]=d-i;c[3]=0;c[4]=k-f;c[5]=1-(a+g);c[6]=e+h;c[7]=0;c[8]=d+i;c[9]=e-h;c[10]=1-(a+l);c[11]=0;c[12]=b[0];c[13]=b[1];c[14]=b[2];c[15]=1;return c};.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+", "+a[4]+", "+a[5]+", "+a[6]+", "+a[7]+", "+a[8]+", "+a[9]+", "+a[10]+", "+a[11]+", "+a[12]+", "+a[13]+", "+a[14]+", "+a[15]+"]"};quat4.create=function(a){var b=new MatrixArray(4);a&&(b[0]=a[0],b[1]=a[1],b[2]=a[2],b[3]=a[3]);return b};quat4.set=function(a,b){b[0]=a[0];b[1]=a[1];b[2]=a[2];b[3]=a[3];return b};.calculateW=function(a,b){var c=a[0],d=a[1],e=a[2];if(!b||a===b)return a[3]=-Math.sqrt(Math.abs(1-c*c-d*d-e*e)),a;b[0]=c;b[1]=d;b[2]=e;b[3]=-Math.sqrt(Math.abs(1-c*c-d*d-e*e));return b};quat4.inverse=function(a,b){if(!b||a===b)return a[0]*=-1,a[1]*=-1,a[2]*=-1,a;b[0]=-a[0];b[1]=-a[1];b[2]=-a[2];b[3]=a[3];return b};quat4.length=function(a){var b=a[0],c=a[1],d=a[2],a=a[3];return Math.sqrt(b*b+c*c+d*d+a*a)};.normalize=function(a,b){b||(b=a);var c=a[0],d=a[1],e=a[2],g=a[3],f=Math.sqrt(c*c+d*d+e*e+g*g);if(f===0)return b[0]=0,b[1]=0,b[2]=0,b[3]=0,b;f=1/f;b[0]=c*f;b[1]=d*f;b[2]=e*f;b[3]=g*f;return b};quat4.multiply=function(a,b,c){c||(c=a);var d=a[0],e=a[1],g=a[2],a=a[3],f=b[0],h=b[1],i=b[2],b=b[3];c[0]=d*b+a*f+e*i-g*h;c[1]=e*b+a*h+g*f-d*i;c[2]=g*b+a*i+d*h-e*f;c[3]=a*b-d*f-e*h-g*i;return c};.multiplyVec3=function(a,b,c){c||(c=b);var d=b[0],e=b[1],g=b[2],b=a[0],f=a[1],h=a[2],a=a[3],i=a*d+f*g-h*e,j=a*e+h*d-b*g,k=a*g+b*e-f*d,d=-b*d-f*e-h*g;c[0]=i*a+d*-b+j*-h-k*-f;c[1]=j*a+d*-f+k*-b-i*-h;c[2]=k*a+d*-h+i*-f-j*-b;return c};quat4.toMat3=function(a,b){b||(b=mat3.create());var c=a[0],d=a[1],e=a[2],g=a[3],f=c+c,h=d+d,i=e+e,j=c*f,k=c*h;c*=i;var l=d*h;d*=i;e*=i;f*=g;h*=g;g*=i;b[0]=1-(l+e);b[1]=k+g;b[2]=c-h;b[3]=k-g;b[4]=1-(j+e);b[5]=d+f;b[6]=c+h;b[7]=d-f;b[8]=1-(j+l);return b};.toMat4=function(a,b){b||(b=mat4.create());var c=a[0],d=a[1],e=a[2],g=a[3],f=c+c,h=d+d,i=e+e,j=c*f,k=c*h;c*=i;var l=d*h;d*=i;e*=i;f*=g;h*=g;g*=i;b[0]=1-(l+e);b[1]=k+g;b[2]=c-h;b[3]=0;b[4]=k-g;b[5]=1-(j+e);b[6]=d+f;b[7]=0;b[8]=c+h;b[9]=d-f;b[10]=1-(j+l);b[11]=0;b[12]=0;b[13]=0;b[14]=0;b[15]=1;return b};.slerp=function(a,b,c,d){d||(d=a);var e=a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3],g,f;if(Math.abs(e)>=1)return d!==a&&(d[0]=a[0],d[1]=a[1],d[2]=a[2],d[3]=a[3]),d;g=Math.acos(e);f=Math.sqrt(1-e*e);if(Math.abs(f)<0.001)return d[0]=a[0]*0.5+b[0]*0.5,d[1]=a[1]*0.5+b[1]*0.5,d[2]=a[2]*0.5+b[2]*0.5,d[3]=a[3]*0.5+b[3]*0.5,d;e=Math.sin((1-c)*g)/f;c=Math.sin(c*g)/f;d[0]=a[0]*e+b[0]*c;d[1]=a[1]*e+b[1]*c;d[2]=a[2]*e+b[2]*c;d[3]=a[3]*e+b[3]*c;return d};.str=function(a){return"["+a[0]+", "+a[1]+", "+a[2]+", "+a[3]+"]"};

(function()

{

                var MAX_VERTICES = 8000;                                                                                      // equates to 2500 objects being drawn

                var MAX_INDICES = (MAX_VERTICES / 2) * 3;                   // 6 indices for every 4 vertices

                var MAX_POINTS = 8000;

                var MULTI_BUFFERS = 4;                                                                                                          // cycle 4 buffers to try and avoid blocking

                var BATCH_NULL = 0;

                var BATCH_QUAD = 1;

                var BATCH_SETTEXTURE = 2;

                var BATCH_SETOPACITY = 3;

                var BATCH_SETBLEND = 4;

                var BATCH_UPDATEMODELVIEW = 5;

                var BATCH_RENDERTOTEXTURE = 6;

                var BATCH_CLEAR = 7;

                var BATCH_POINTS = 8;

                var BATCH_SETPROGRAM = 9;

                var BATCH_SETPROGRAMPARAMETERS = 10;

                var BATCH_SETTEXTURE1 = 11;

                var BATCH_SETCOLOR = 12;

                var BATCH_SETDEPTHTEST = 13;

                var BATCH_SETEARLYZMODE = 14;

                /*

                var lose_ext = null;

                window.lose_context = function ()

                {

                               if (!lose_ext)

                               {

                                               console.log("WEBGL_lose_context not supported");

                                               return;

                               }

                               lose_ext.loseContext();

                };

                window.restore_context = function ()

                {

                               if (!lose_ext)

                               {

                                               console.log("WEBGL_lose_context not supported");

                                               return;

                               }

                               lose_ext.restoreContext();

                };

                */

                var tempMat4 = mat4.create();

                function GLWrap_(gl, isMobile, enableFrontToBack)

                {

                               this.isIE = /msie/i.test(navigator.userAgent) || /trident/i.test(navigator.userAgent);

                               this.width = 0;                      // not yet known, wait for call to setSize()

                               this.height = 0;

                               this.enableFrontToBack =!!enableFrontToBack;

                               this.isEarlyZPass = false;

                               this.isBatchInEarlyZPass = false;

                               this.currentZ = 0;

                               this.zNear = 1;

                               this.zFar = 1000;

                               this.zIncrement = ((this.zFar - this.zNear) / 32768);

                               this.zA = this.zFar / (this.zFar - this.zNear);

                               this.zB = this.zFar * this.zNear / (this.zNear - this.zFar);

                               this.kzA = 65536 * this.zA;

                               this.kzB = 65536 * this.zB;

                               this.cam = vec3.create([0, 0, 100]);                                               // camera position

                               this.look = vec3.create([0, 0, 0]);                                                    // lookat position

                               this.up = vec3.create([0, 1, 0]);                                                       // up vector

                               this.worldScale = vec3.create([1, 1, 1]);                        // world scaling factor

                               this.enable_mipmaps = true;

                               this.matP = mat4.create();                                                                                              // perspective matrix

                               this.matMV = mat4.create();                                                                                         // model view matrix

                               this.lastMV = mat4.create();

                               this.currentMV = mat4.create();

                               this.gl = gl;

                               this.version = (this.gl.getParameter(this.gl.VERSION).indexOf("WebGL 2") === 0 ? 2: 1);

                               this.initState();

                };

                GLWrap_.prototype.initState = function ()

                               var gl = this.gl;

                               var i, len;

                               this.lastOpacity = 1;

                               this.lastTexture0 = null;                                    // last bound to TEXTURE0

                               this.lastTexture1 = null;                                    // last bound to TEXTURE1

                               this.currentOpacity = 1;

                               gl.clearColor(0, 0, 0, 0);

                               gl.clear(gl.COLOR_BUFFER_BIT);

                               gl.enable(gl.BLEND);.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

                               gl.disable(gl.CULL_FACE);

                               gl.disable(gl.STENCIL_TEST);

                               gl.disable(gl.DITHER);

                               if (this.enableFrontToBack)

                               {

                                               gl.enable(gl.DEPTH_TEST);

                                               gl.depthFunc(gl.LEQUAL);

                               }

                               else

                               {

                                               gl.disable(gl.DEPTH_TEST);

                               }

                               this.maxTextureSize = gl.getParameter(gl.MAX_TEXTURE_SIZE);

                               this.lastSrcBlend = gl.ONE;

                               this.lastDestBlend = gl.ONE_MINUS_SRC_ALPHA;

                               this.vertexData = new Float32Array(MAX_VERTICES * (this.enableFrontToBack ? 3: 2));

                               this.texcoordData = new Float32Array(MAX_VERTICES * 2);

                               this.pointData = new Float32Array(MAX_POINTS * 4);

                               this.pointBuffer = gl.createBuffer();

                               gl.bindBuffer(gl.ARRAY_BUFFER, this.pointBuffer);

                               gl.bufferData(gl.ARRAY_BUFFER, this.pointData.byteLength, gl.DYNAMIC_DRAW);

                               this.vertexBuffers = new Array(MULTI_BUFFERS);

                               this.texcoordBuffers = new Array(MULTI_BUFFERS);

                               for (i = 0; i < MULTI_BUFFERS; i++)

                               {

                                               this.vertexBuffers[i] = gl.createBuffer();

                                               gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffers[i]);

                                               gl.bufferData(gl.ARRAY_BUFFER, this.vertexData.byteLength, gl.DYNAMIC_DRAW);

                                               this.texcoordBuffers[i] = gl.createBuffer();

                                               gl.bindBuffer(gl.ARRAY_BUFFER, this.texcoordBuffers[i]);

                                               gl.bufferData(gl.ARRAY_BUFFER, this.texcoordData.byteLength, gl.DYNAMIC_DRAW);

                               }

                               this.curBuffer = 0;

                               this.indexBuffer = gl.createBuffer();

                               gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);

                               var indexData = new Uint16Array(MAX_INDICES);

                               i = 0, len = MAX_INDICES;

                               var fv = 0;

                               while (i < len)

                               {

                                               indexData[i++] = fv;                          // top left

                                               indexData[i++] = fv + 1;   // top right

                                               indexData[i++] = fv + 2;   // bottom right (first tri)

                                               indexData[i++] = fv;                          // top left

                                               indexData[i++] = fv + 2;   // bottom right

                                               indexData[i++] = fv + 3;   // bottom left

                                               fv += 4;

                               }

                               gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, indexData, gl.STATIC_DRAW);

                               this.vertexPtr = 0;

                               this.texPtr = 0;

                               this.pointPtr = 0;

                               var fsSource, vsSource;

                               this.shaderPrograms = [];

                               fsSource = [

                                               "varying mediump vec2 vTex;",

                                               "uniform lowp float opacity;",

                                               "uniform lowp sampler2D samplerFront;",

                                               "void main(void) {",

                                               "              gl_FragColor = texture2D(samplerFront, vTex);",

                                               "              gl_FragColor *= opacity;",

                                               "}"

                               ].join("\n");

                               if (this.enableFrontToBack)

                               {

                                               vsSource = [

                                                               "attribute highp vec3 aPos;",

                                                               "attribute mediump vec2 aTex;",

                                                               "varying mediump vec2 vTex;",

                                                               "uniform highp mat4 matP;",

                                                               "uniform highp mat4 matMV;",

                                                               "void main(void) {",

                                                               "              gl_Position = matP * matMV * vec4(aPos.x, aPos.y, aPos.z, 1.0);",

                                                               "              vTex = aTex;",

                                                               "}"

                                               ].join("\n");

                               }

                               else

                               {

                                               vsSource = [

                                                               "attribute highp vec2 aPos;",

                                                               "attribute mediump vec2 aTex;",

                                                               "varying mediump vec2 vTex;",

                                                               "uniform highp mat4 matP;",

                                                               "uniform highp mat4 matMV;",

                                                               "void main(void) {",

                                                               "              gl_Position = matP * matMV * vec4(aPos.x, aPos.y, 0.0, 1.0);",

                                                               "              vTex = aTex;",

                                                               "}"

                                               ].join("\n");

                                }

                               var shaderProg = this.createShaderProgram({src: fsSource}, vsSource, "<default>");

;

                               this.shaderPrograms.push(shaderProg);                        // Default shader is always shader 0

                               fsSource = [

                                               "uniform mediump sampler2D samplerFront;",

                                               "varying lowp float opacity;",

                                               "void main(void) {",

                                               "              gl_FragColor = texture2D(samplerFront, gl_PointCoord);",

                                               "              gl_FragColor *= opacity;",

                                               "}"

                               ].join("\n");

                               var pointVsSource = [

                                               "attribute vec4 aPos;",

                                               "varying float opacity;",

                                               "uniform mat4 matP;",

                                               "uniform mat4 matMV;",

                                               "void main(void) {",

                                               "              gl_Position = matP * matMV * vec4(aPos.x, aPos.y, 0.0, 1.0);",

                                               "              gl_PointSize = aPos.z;",

                                               "              opacity = aPos.w;",

                                               "}"

                               ].join("\n");

                               shaderProg = this.createShaderProgram({src: fsSource}, pointVsSource, "<point>");

;

                               this.shaderPrograms.push(shaderProg);                        // Point shader is always shader 1

                               fsSource = [

                                               "varying mediump vec2 vTex;",

                                               "uniform lowp sampler2D samplerFront;",

                                               "void main(void) {",

                                               "              if (texture2D(samplerFront, vTex).a < 1.0)",

                                               "                              discard;",                                                                                             // discarding non-opaque fragments

                                               "}"

                               ].join("\n");

                               var shaderProg = this.createShaderProgram({src: fsSource}, vsSource, "<earlyz>");

;

                               this.shaderPrograms.push(shaderProg);                        // Early-Z shader is always shader 2

                               fsSource = [

                                               "uniform lowp vec4 colorFill;",

                                               "void main(void) {",

                                               "              gl_FragColor = colorFill;",

                                               "}"

                               ].join("\n");

                               var shaderProg = this.createShaderProgram({src: fsSource}, vsSource, "<fill>");

;

                               this.shaderPrograms.push(shaderProg);                        // Fill-color shader is always shader 3

                               for (var shader_name in cr.shaders)

                               {

                                               if (cr.shaders.hasOwnProperty(shader_name))

                                                               this.shaderPrograms.push(this.createShaderProgram(cr.shaders[shader_name], vsSource, shader_name));

                               }

                               gl.activeTexture(gl.TEXTURE0);

                               gl.bindTexture(gl.TEXTURE_2D, null);

                               this.batch = [];

                               this.batchPtr = 0;

                               this.hasQuadBatchTop = false;

                               this.hasPointBatchTop = false;

                               this.lastProgram = -1;                                                        // start -1 so first switchProgram can do work

                               this.currentProgram = -1;                                  // current program during batch execution

                               this.currentShader = null;

                               this.fbo = gl.createFramebuffer();

                               this.renderToTex = null;

                               this.depthBuffer = null;

                               this.attachedDepthBuffer = false;  // wait until first size call to attach, otherwise it has no storage

                               if (this.enableFrontToBack)

                               {

                                               this.depthBuffer = gl.createRenderbuffer();

                               }

                               this.tmpVec3 = vec3.create([0, 0, 0]);

;

                               var pointsizes = gl.getParameter(gl.ALIASED_POINT_SIZE_RANGE);

                               this.minPointSize = pointsizes[0];

                               this.maxPointSize = pointsizes[1];

                               if (this.maxPointSize > 2048)

                                               this.maxPointSize = 2048;

;

                               this.switchProgram(0);

                               cr.seal(this);

                };

                function GLShaderProgram(gl, shaderProgram, name)

                {

                               this.gl = gl;

                               this.shaderProgram = shaderProgram;

                               this.name = name;

                               this.locAPos = gl.getAttribLocation(shaderProgram, "aPos");

                               this.locATex = gl.getAttribLocation(shaderProgram, "aTex");

                               this.locMatP = gl.getUniformLocation(shaderProgram, "matP");

                               this.locMatMV = gl.getUniformLocation(shaderProgram, "matMV");

                               this.locOpacity = gl.getUniformLocation(shaderProgram, "opacity");

                               this.locColorFill = gl.getUniformLocation(shaderProgram, "colorFill");

                               this.locSamplerFront = gl.getUniformLocation(shaderProgram, "samplerFront");

                               this.locSamplerBack = gl.getUniformLocation(shaderProgram, "samplerBack");

                               this.locDestStart = gl.getUniformLocation(shaderProgram, "destStart");

                               this.locDestEnd = gl.getUniformLocation(shaderProgram, "destEnd");

                               this.locSeconds = gl.getUniformLocation(shaderProgram, "seconds");

                               this.locPixelWidth = gl.getUniformLocation(shaderProgram, "pixelWidth");

                               this.locPixelHeight = gl.getUniformLocation(shaderProgram, "pixelHeight");

                               this.locLayerScale = gl.getUniformLocation(shaderProgram, "layerScale");

                               this.locLayerAngle = gl.getUniformLocation(shaderProgram, "layerAngle");

                               this.locViewOrigin = gl.getUniformLocation(shaderProgram, "viewOrigin");

                               this.locScrollPos = gl.getUniformLocation(shaderProgram, "scrollPos");

                               this.hasAnyOptionalUniforms =!!(this.locPixelWidth || this.locPixelHeight || this.locSeconds || this.locSamplerBack || this.locDestStart || this.locDestEnd || this.locLayerScale || this.locLayerAngle || this.locViewOrigin || this.locScrollPos);

                               this.lpPixelWidth = -999;                  // set to something unlikely so never counts as cached on first set

                               this.lpPixelHeight = -999;

                               this.lpOpacity = 1;

                               this.lpDestStartX = 0.0;

                               this.lpDestStartY = 0.0;

                               this.lpDestEndX = 1.0;

                               this.lpDestEndY = 1.0;

                               this.lpLayerScale = 1.0;

                               this.lpLayerAngle = 0.0;

                               this.lpViewOriginX = 0.0;

                               this.lpViewOriginY = 0.0;

                               this.lpScrollPosX = 0.0;

                               this.lpScrollPosY = 0.0;

                               this.lpSeconds = 0.0;

                               this.lastCustomParams = [];

                               this.lpMatMV = mat4.create();

                               if (this.locOpacity)

                                               gl.uniform1f(this.locOpacity, 1);

                               if (this.locColorFill)

                                               gl.uniform4f(this.locColorFill, 1.0, 1.0, 1.0, 1.0);

                               if (this.locSamplerFront)

                                               gl.uniform1i(this.locSamplerFront, 0);

                               if (this.locSamplerBack)

                                               gl.uniform1i(this.locSamplerBack, 1);

                               if (this.locDestStart)

                                               gl.uniform2f(this.locDestStart, 0.0, 0.0);

                               if (this.locDestEnd)

                                               gl.uniform2f(this.locDestEnd, 1.0, 1.0);

                               if (this.locLayerScale)

                                               gl.uniform1f(this.locLayerScale, 1.0);

                               if (this.locLayerAngle)

                                               gl.uniform1f(this.locLayerAngle, 0.0);

                               if (this.locViewOrigin)

                                               gl.uniform2f(this.locViewOrigin, 0.0, 0.0);

                               if (this.locScrollPos)

                                               gl.uniform2f(this.locScrollPos, 0.0, 0.0);

                               if (this.locSeconds)

                                               gl.uniform1f(this.locSeconds, 0.0);

                               this.hasCurrentMatMV = false;                      // matMV needs updating

                };

                function areMat4sEqual(a, b)

                {

                               return a[0]===b[0]&&a[1]===b[1]&&a[2]===b[2]&&a[3]===b[3]&&

                                                a[4]===b[4]&&a[5]===b[5]&&a[6]===b[6]&&a[7]===b[7]&&

                                                a[8]===b[8]&&a[9]===b[9]&&a[10]===b[10]&&a[11]===b[11]&&

                                                a[12]===b[12]&&a[13]===b[13]&&a[14]===b[14]&&a[15]===b[15];

                };

                GLShaderProgram.prototype.updateMatMV = function (mv)

                {

                               if (areMat4sEqual(this.lpMatMV, mv))

                                               return;                   // no change, save the expensive GL call

                               mat4.set(mv, this.lpMatMV);

                               this.gl.uniformMatrix4fv(this.locMatMV, false, mv);

                };

                GLWrap_.prototype.createShaderProgram = function(shaderEntry, vsSource, name)

                {

                               var gl = this.gl;

                               var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);

                               gl.shaderSource(fragmentShader, shaderEntry.src);

                               gl.compileShader(fragmentShader);

                               if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS))

                               {

;

                                               gl.deleteShader(fragmentShader);

                                               return null;

                               }

                               var vertexShader = gl.createShader(gl.VERTEX_SHADER);

                               gl.shaderSource(vertexShader, vsSource);

                               gl.compileShader(vertexShader);

                               if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS))

                               {

;

                                               gl.deleteShader(fragmentShader);

                                               gl.deleteShader(vertexShader);

                                               return null;

                               }

                               var shaderProgram = gl.createProgram();

                               gl.attachShader(shaderProgram, fragmentShader);

                               gl.attachShader(shaderProgram, vertexShader);

                               gl.linkProgram(shaderProgram);

                               if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS))

                               {

;

                                               gl.deleteShader(fragmentShader);

                                               gl.deleteShader(vertexShader);

                                               gl.deleteProgram(shaderProgram);

                                               return null;

                               }

                               gl.useProgram(shaderProgram);

                               gl.deleteShader(fragmentShader);

                               gl.deleteShader(vertexShader);

                               var ret = new GLShaderProgram(gl, shaderProgram, name);

                               ret.extendBoxHorizontal = shaderEntry.extendBoxHorizontal || 0;

                               ret.extendBoxVertical = shaderEntry.extendBoxVertical || 0;

                               ret.crossSampling =!!shaderEntry.crossSampling;

                               ret.preservesOpaqueness =!!shaderEntry.preservesOpaqueness;

                               ret.animated =!!shaderEntry.animated;

                               ret.parameters = shaderEntry.parameters || [];

                               var i, len;

                               for (i = 0, len = ret.parameters.length; i < len; i++)

                               {

                                               ret.parameters[i][1] = gl.getUniformLocation(shaderProgram, ret.parameters[i][0]);

                                               ret.lastCustomParams.push(0);

                                               gl.uniform1f(ret.parameters[i][1], 0);

                               }

                               cr.seal(ret);

                               return ret;

                };

                GLWrap_.prototype.getShaderIndex = function(name_)

                {

                               var i, len;

                               for (i = 0, len = this.shaderPrograms.length; i < len; i++)

                               {

                                               if (this.shaderPrograms[i].name === name_)

                                                               return i;

                               }

                               return -1;

                };

                GLWrap_.prototype.project = function (x, y, out)

                {

                               var mv = this.matMV;

                               var proj = this.matP;

                               var fTempo = [0, 0, 0, 0, 0, 0, 0, 0];

                               fTempo[0] = mv[0]*x+mv[4]*y+mv[12];

                               fTempo[1] = mv[1]*x+mv[5]*y+mv[13];

                               fTempo[2] = mv[2]*x+mv[6]*y+mv[14];

                               fTempo[3] = mv[3]*x+mv[7]*y+mv[15];

                               fTempo[4] = proj[0]*fTempo[0]+proj[4]*fTempo[1]+proj[8]*fTempo[2]+proj[12]*fTempo[3];

                               fTempo[5] = proj[1]*fTempo[0]+proj[5]*fTempo[1]+proj[9]*fTempo[2]+proj[13]*fTempo[3];

                               fTempo[6] = proj[2]*fTempo[0]+proj[6]*fTempo[1]+proj[10]*fTempo[2]+proj[14]*fTempo[3];

                               fTempo[7] = -fTempo[2];

                               if(fTempo[7]===0.0)         //The w value

                                               return;

                               fTempo[7]=1.0/fTempo[7];

                               fTempo[4]*=fTempo[7];

                               fTempo[5]*=fTempo[7];

                               fTempo[6]*=fTempo[7];

                               out[0]=(fTempo[4]*0.5+0.5)*this.width;

                               out[1]=(fTempo[5]*0.5+0.5)*this.height;

                };

                GLWrap_.prototype.setSize = function(w, h, force)

                {

                               if (this.width === w && this.height === h &&!force)

                                               return;

                               this.endBatch();

                               var gl = this.gl;

                               this.width = w;

                               this.height = h;

                               gl.viewport(0, 0, w, h);

                               if (this.enableFrontToBack)

                               {

                                               mat4.ortho(-w/2, w/2, h/2, -h/2, this.zNear, this.zFar, this.matP);

                                               this.worldScale[0] = 1;

                                               this.worldScale[1] = 1;

                               }

                               else

                               {

                                               mat4.perspective(45, w / h, this.zNear, this.zFar, this.matP);

                                               var tl = [0, 0];

                                               var br = [0, 0];

                                               this.project(0, 0, tl);

                                               this.project(1, 1, br);

                                               this.worldScale[0] = 1 / (br[0] - tl[0]);

                                               this.worldScale[1] = -1 / (br[1] - tl[1]);

                               }

                               var i, len, s;

                               for (i = 0, len = this.shaderPrograms.length; i < len; i++)

                               {

                                               s = this.shaderPrograms[i];

                                               s.hasCurrentMatMV = false;

                                               if (s.locMatP)

                                               {

                                                               gl.useProgram(s.shaderProgram);

                                                               gl.uniformMatrix4fv(s.locMatP, false, this.matP);

                                               }

                               }

                               gl.useProgram(this.shaderPrograms[this.lastProgram].shaderProgram);

                               gl.bindTexture(gl.TEXTURE_2D, null);

                               gl.activeTexture(gl.TEXTURE1);

                               gl.bindTexture(gl.TEXTURE_2D, null);

                               gl.activeTexture(gl.TEXTURE0);

                               this.lastTexture0 = null;

                               this.lastTexture1 = null;

                               if (this.depthBuffer)

                               {

                                               gl.bindFramebuffer(gl.FRAMEBUFFER, this.fbo);

                                               gl.bindRenderbuffer(gl.RENDERBUFFER, this.depthBuffer);

                                               gl.renderbufferStorage(gl.RENDERBUFFER, gl.DEPTH_COMPONENT16, this.width, this.height);

                                               if (!this.attachedDepthBuffer)

                                               {

                                                               gl.framebufferRenderbuffer(gl.FRAMEBUFFER, gl.DEPTH_ATTACHMENT, gl.RENDERBUFFER, this.depthBuffer);

                                                               this.attachedDepthBuffer = true;

                                               }

                                               gl.bindRenderbuffer(gl.RENDERBUFFER, null);

                                               gl.bindFramebuffer(gl.FRAMEBUFFER, null);

                                               this.renderToTex = null;

                               }

                };

                GLWrap_.prototype.resetModelView = function ()

                {

                               mat4.lookAt(this.cam, this.look, this.up, this.matMV);

                               mat4.scale(this.matMV, this.worldScale);

                };

                GLWrap_.prototype.translate = function (x, y)

                {

                               if (x === 0 && y === 0)

                                               return;

                               this.tmpVec3[0] = x;// * this.worldScale[0];

                               this.tmpVec3[1] = y;// * this.worldScale[1];

                               this.tmpVec3[2] = 0;

                               mat4.translate(this.matMV, this.tmpVec3);

                };

                GLWrap_.prototype.scale = function (x, y)

                {

                               if (x === 1 && y === 1)

                                               return;

                               this.tmpVec3[0] = x;

                               this.tmpVec3[1] = y;

                               this.tmpVec3[2] = 1;

                               mat4.scale(this.matMV, this.tmpVec3);

                };

                GLWrap_.prototype.rotateZ = function (a)

                {

                               if (a === 0)

                                               return;

                               mat4.rotateZ(this.matMV, a);

                };

                GLWrap_.prototype.updateModelView = function()

                {

                               if (areMat4sEqual(this.lastMV, this.matMV))

                                               return;

                               var b = this.pushBatch();

                               b.type = BATCH_UPDATEMODELVIEW;

                               if (b.mat4param)

                                               mat4.set(this.matMV, b.mat4param);

                               else

                                               b.mat4param = mat4.create(this.matMV);

                               mat4.set(this.matMV, this.lastMV);

                               this.hasQuadBatchTop = false;

                               this.hasPointBatchTop = false;

                };

                /*

                var debugBatch = false;

                jQuery(document).mousedown(

                               function(info) {

                                               if (info.which === 2)

                                                               debugBatch = true;

                               }

                );

                */

                GLWrap_.prototype.setEarlyZIndex = function (i)

                {

                               if (!this.enableFrontToBack)

                                               return;

                               if (i > 32760)

                                               i = 32760;

                               this.currentZ = this.cam[2] - this.zNear - i * this.zIncrement;

                };

                function GLBatchJob(type_, glwrap_)

                {

                               this.type = type_;

                               this.glwrap = glwrap_;

                               this.gl = glwrap_.gl;

                               this.opacityParam = 0;                      // for setOpacity()

                               this.startIndex = 0;                             // for quad()

                               this.indexCount = 0;                          // "

                               this.texParam = null;                         // for setTexture()

                               this.mat4param = null;                     // for updateModelView()

                               this.shaderParams = [];                     // for user parameters

                               cr.seal(this);

                };

                GLBatchJob.prototype.doSetEarlyZPass = function ()

                {

                               var gl = this.gl;

                               var glwrap = this.glwrap;

                               if (this.startIndex!== 0)                     // enable

                               {

                                               gl.depthMask(true);                                           // enable depth writes

                                               gl.colorMask(false, false, false, false);          // disable color writes

                                               gl.disable(gl.BLEND);                        // no color writes so disable blend

                                               gl.bindFramebuffer(gl.FRAMEBUFFER, glwrap.fbo);

                                               gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, null, 0);

                                               gl.clear(gl.DEPTH_BUFFER_BIT);                               // auto-clear depth buffer

                                               gl.bindFramebuffer(gl.FRAMEBUFFER, null);

                                               glwrap.isBatchInEarlyZPass = true;

                               }

                               else

                               {

                                               gl.depthMask(false);                         // disable depth writes, only test existing depth values

                                               gl.colorMask(true, true, true, true);                 // enable color writes

                                               gl.enable(gl.BLEND);                        // turn blending back on

                                               glwrap.isBatchInEarlyZPass = false;

                               }

                };

                GLBatchJob.prototype.doSetTexture = function ()

                {

                               this.gl.bindTexture(this.gl.TEXTURE_2D, this.texParam);

                };

                GLBatchJob.prototype.doSetTexture1 = function ()

                {

                               var gl = this.gl;

                               gl.activeTexture(gl.TEXTURE1);

                               gl.bindTexture(gl.TEXTURE_2D, this.texParam);

                               gl.activeTexture(gl.TEXTURE0);

                };

                GLBatchJob.prototype.doSetOpacity = function ()

                {

                               var o = this.opacityParam;

                               var glwrap = this.glwrap;

                               glwrap.currentOpacity = o;

                               var curProg = glwrap.currentShader;

                               if (curProg.locOpacity && curProg.lpOpacity!== o)

                               {

                                               curProg.lpOpacity = o;

                                               this.gl.uniform1f(curProg.locOpacity, o);

                               }

                };

                GLBatchJob.prototype.doQuad = function ()

                {

                               this.gl.drawElements(this.gl.TRIANGLES, this.indexCount, this.gl.UNSIGNED_SHORT, this.startIndex);

                };

                GLBatchJob.prototype.doSetBlend = function ()

                {

                               this.gl.blendFunc(this.startIndex, this.indexCount);

                };

                GLBatchJob.prototype.doUpdateModelView = function ()

                {

                               var i, len, s, shaderPrograms = this.glwrap.shaderPrograms, currentProgram = this.glwrap.currentProgram;

                               for (i = 0, len = shaderPrograms.length; i < len; i++)

                               {

                                               s = shaderPrograms[i];

                                               if (i === currentProgram && s.locMatMV)

                                               {

                                                               s.updateMatMV(this.mat4param);

                                                               s.hasCurrentMatMV = true;

                                               }

                                               else

                                                               s.hasCurrentMatMV = false;

                               }

                               mat4.set(this.mat4param, this.glwrap.currentMV);

                };

                GLBatchJob.prototype.doRenderToTexture = function ()

                {

                               var gl = this.gl;

                               var glwrap = this.glwrap;

                               if (this.texParam)

                               {

                                               if (glwrap.lastTexture1 === this.texParam)

                                               {

                                                               gl.activeTexture(gl.TEXTURE1);

                                                               gl.bindTexture(gl.TEXTURE_2D, null);

                                                               glwrap.lastTexture1 = null;

                                                               gl.activeTexture(gl.TEXTURE0);

                                               }

                                               gl.bindFramebuffer(gl.FRAMEBUFFER, glwrap.fbo);

                                               if (!glwrap.isBatchInEarlyZPass)

                                               {

                                                               gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.texParam, 0);

                                               }

                               }

                               else

                               {

                                               if (!glwrap.enableFrontToBack)

                                               {

                                                               gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, null, 0);

                                               }

                                               gl.bindFramebuffer(gl.FRAMEBUFFER, null);

                               }

                };

                GLBatchJob.prototype.doClear = function ()

                {

                               var gl = this.gl;

                               var mode = this.startIndex;

                               if (mode === 0)                                  // clear whole surface

                               {

                                               gl.clearColor(this.mat4param[0], this.mat4param[1], this.mat4param[2], this.mat4param[3]);

                                               gl.clear(gl.COLOR_BUFFER_BIT);

                               }

                               else if (mode === 1)           // clear rectangle

                               {

                                               gl.enable(gl.SCISSOR_TEST);

                                               gl.scissor(this.mat4param[0], this.mat4param[1], this.mat4param[2], this.mat4param[3]);

                                               gl.clearColor(0, 0, 0, 0);

                                               gl.clear(gl.COLOR_BUFFER_BIT);

                                               gl.disable(gl.SCISSOR_TEST);

                               }

                               else                                                                        // clear depth

                               {

                                               gl.clear(gl.DEPTH_BUFFER_BIT);

                               }

                };

                GLBatchJob.prototype.doSetDepthTestEnabled = function ()

                {

                               var gl = this.gl;

                               var enable = this.startIndex;

                               if (enable!== 0)

                               {

                                               gl.enable(gl.DEPTH_TEST);

                               }

                               else

                               {

                                               gl.disable(gl.DEPTH_TEST);

                               }

                };

                GLBatchJob.prototype.doPoints = function ()

                {

                               var gl = this.gl;

                               var glwrap = this.glwrap;

                               if (glwrap.enableFrontToBack)

                                               gl.disable(gl.DEPTH_TEST);

                               var s = glwrap.shaderPrograms[1];

                               gl.useProgram(s.shaderProgram);

                               if (!s.hasCurrentMatMV && s.locMatMV)

                               {

                                               s.updateMatMV(glwrap.currentMV);

                                               s.hasCurrentMatMV = true;

                               }

                               gl.enableVertexAttribArray(s.locAPos);

                               gl.bindBuffer(gl.ARRAY_BUFFER, glwrap.pointBuffer);

                               gl.vertexAttribPointer(s.locAPos, 4, gl.FLOAT, false, 0, 0);

                               gl.drawArrays(gl.POINTS, this.startIndex / 4, this.indexCount);

                               s = glwrap.currentShader;

                               gl.useProgram(s.shaderProgram);

                               if (s.locAPos >= 0)

                               {

                                               gl.enableVertexAttribArray(s.locAPos);

                                               gl.bindBuffer(gl.ARRAY_BUFFER, glwrap.vertexBuffers[glwrap.curBuffer]);

                                               gl.vertexAttribPointer(s.locAPos, glwrap.enableFrontToBack ? 3: 2, gl.FLOAT, false, 0, 0);

                               }

                               if (s.locATex >= 0)

                               {

                                               gl.enableVertexAttribArray(s.locATex);

                                               gl.bindBuffer(gl.ARRAY_BUFFER, glwrap.texcoordBuffers[glwrap.curBuffer]);

                                               gl.vertexAttribPointer(s.locATex, 2, gl.FLOAT, false, 0, 0);

                               }

                               if (glwrap.enableFrontToBack)

                                               gl.enable(gl.DEPTH_TEST);

                };

                GLBatchJob.prototype.doSetProgram = function ()

                {

                               var gl = this.gl;

                               var glwrap = this.glwrap;

                               var s = glwrap.shaderPrograms[this.startIndex];                         // recycled param to save memory

                               glwrap.currentProgram = this.startIndex;                                     // current batch program

                               glwrap.currentShader = s;

                               gl.useProgram(s.shaderProgram);                                                                                  // switch to

                               if (!s.hasCurrentMatMV && s.locMatMV)

                               {

                                               s.updateMatMV(glwrap.currentMV);

                                               s.hasCurrentMatMV = true;

                               }

                               if (s.locOpacity && s.lpOpacity!== glwrap.currentOpacity)

                               {

                                               s.lpOpacity = glwrap.currentOpacity;

                                               gl.uniform1f(s.locOpacity, glwrap.currentOpacity);

                               }

                               if (s.locAPos >= 0)

                               {

                                               gl.enableVertexAttribArray(s.locAPos);

                                               gl.bindBuffer(gl.ARRAY_BUFFER, glwrap.vertexBuffers[glwrap.curBuffer]);

                                               gl.vertexAttribPointer(s.locAPos, glwrap.enableFrontToBack ? 3: 2, gl.FLOAT, false, 0, 0);

                               }

                               if (s.locATex >= 0)

                               {

                                               gl.enableVertexAttribArray(s.locATex);

                                               gl.bindBuffer(gl.ARRAY_BUFFER, glwrap.texcoordBuffers[glwrap.curBuffer]);

                                               gl.vertexAttribPointer(s.locATex, 2, gl.FLOAT, false, 0, 0);

                               }

                }

                GLBatchJob.prototype.doSetColor = function ()

                {

                               var s = this.glwrap.currentShader;

                               var mat4param = this.mat4param;

                               this.gl.uniform4f(s.locColorFill, mat4param[0], mat4param[1], mat4param[2], mat4param[3]);

                };

                GLBatchJob.prototype.doSetProgramParameters = function ()

                {

                               var i, len, s = this.glwrap.currentShader;

                               var gl = this.gl;

                               var mat4param = this.mat4param;

                               if (s.locSamplerBack && this.glwrap.lastTexture1!== this.texParam)

                               {

                                               gl.activeTexture(gl.TEXTURE1);

                                               gl.bindTexture(gl.TEXTURE_2D, this.texParam);

                                               this.glwrap.lastTexture1 = this.texParam;

                                               gl.activeTexture(gl.TEXTURE0);

                               }

                               var v = mat4param[0];

                               var v2;

                               if (s.locPixelWidth && v!== s.lpPixelWidth)

                               {

                                               s.lpPixelWidth = v;

                                               gl.uniform1f(s.locPixelWidth, v);

                               }

                               v = mat4param[1];

                               if (s.locPixelHeight && v!== s.lpPixelHeight)

                               {

                                               s.lpPixelHeight = v;

                                               gl.uniform1f(s.locPixelHeight, v);

                               v = mat4param[2];

                               v2 = mat4param[3];

                               if (s.locDestStart && (v!== s.lpDestStartX || v2!== s.lpDestStartY))

                               {

                                               s.lpDestStartX = v;

                                               s.lpDestStartY = v2;

                                               gl.uniform2f(s.locDestStart, v, v2);

                               }

                               v = mat4param[4];

                               v2 = mat4param[5];

                               if (s.locDestEnd && (v!== s.lpDestEndX || v2!== s.lpDestEndY))

                               {

                                               s.lpDestEndX = v;

                                               s.lpDestEndY = v2;

                                               gl.uniform2f(s.locDestEnd, v, v2);

                               }

                               v = mat4param[6];

                               if (s.locLayerScale && v!== s.lpLayerScale)

                               {

                                               s.lpLayerScale = v;

                                               gl.uniform1f(s.locLayerScale, v);

                               }

                               v = mat4param[7];

                               if (s.locLayerAngle && v!== s.lpLayerAngle)

                               {

                                               s.lpLayerAngle = v;

                                               gl.uniform1f(s.locLayerAngle, v);

                               }

                               v = mat4param[8];

                               v2 = mat4param[9];

                               if (s.locViewOrigin && (v!== s.lpViewOriginX || v2!== s.lpViewOriginY))

                               {

                                               s.lpViewOriginX = v;

                                               s.lpViewOriginY = v2;

                                               gl.uniform2f(s.locViewOrigin, v, v2);

                               }

                               v = mat4param[10];

                               v2 = mat4param[11];

                               if (s.locScrollPos && (v!== s.lpScrollPosX || v2!== s.lpScrollPosY))

                               {

                                               s.lpScrollPosX = v;

                                               s.lpScrollPosY = v2;

                                               gl.uniform2f(s.locScrollPos, v, v2);

                               }

                               v = mat4param[12];

                               if (s.locSeconds && v!== s.lpSeconds)

                               {

                                               s.lpSeconds = v;

                                               gl.uniform1f(s.locSeconds, v);

                               }

                               if (s.parameters.length)

                               {

                                               for (i = 0, len = s.parameters.length; i < len; i++)

                                               {

                                                               v = this.shaderParams[i];

                                                               if (v!== s.lastCustomParams[i])

                                                               {

                                                                              s.lastCustomParams[i] = v;

                                                                              gl.uniform1f(s.parameters[i][1], v);

                                                               }

                                               }

                               }

                };

                GLWrap_.prototype.pushBatch = function ()

                {

                               if (this.batchPtr === this.batch.length)

                                               this.batch.push(new GLBatchJob(BATCH_NULL, this));

                               return this.batch[this.batchPtr++];

                };

                GLWrap_.prototype.endBatch = function ()

                {

                               if (this.batchPtr === 0)

                                               return;

                               if (this.gl.isContextLost())

                                               return;

                               var gl = this.gl;

                               if (this.pointPtr > 0)

                               {

                                               gl.bindBuffer(gl.ARRAY_BUFFER, this.pointBuffer);

                                               gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.pointData.subarray(0, this.pointPtr));

                                               if (s && s.locAPos >= 0 && s.name === "<point>")

                                                               gl.vertexAttribPointer(s.locAPos, 4, gl.FLOAT, false, 0, 0);

                               }

                               if (this.vertexPtr > 0)

                               {

                                               var s = this.currentShader;

                                               gl.bindBuffer(gl.ARRAY_BUFFER, this.vertexBuffers[this.curBuffer]);

                                               gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.vertexData.subarray(0, this.vertexPtr));

                                               if (s && s.locAPos >= 0 && s.name!== "<point>")

                                                               gl.vertexAttribPointer(s.locAPos, this.enableFrontToBack ? 3: 2, gl.FLOAT, false, 0, 0);

                                               gl.bindBuffer(gl.ARRAY_BUFFER, this.texcoordBuffers[this.curBuffer]);

                                               gl.bufferSubData(gl.ARRAY_BUFFER, 0, this.texcoordData.subarray(0, this.texPtr));

                                               if (s && s.locATex >= 0 && s.name!== "<point>")

                                                               gl.vertexAttribPointer(s.locATex, 2, gl.FLOAT, false, 0, 0);

                               }

                               var i, len, b;

                               for (i = 0, len = this.batchPtr; i < len; i++)

                               {

                                               b = this.batch[i];

                                               switch (b.type) {

                                               case 1:

                                                               b.doQuad();

                                                               break;

                                               case 2:

                                                               b.doSetTexture();

                                                               break;

                                               case 3:

                                                               b.doSetOpacity();

                                                               break;

                                               case 4:

                                                               b.doSetBlend();

                                                               break;

                                               case 5:

                                                               b.doUpdateModelView();

                                                               break;

                                               case 6:

                                                               b.doRenderToTexture();

                                                               break;

                                               case 7:

                                                               b.doClear();

                                                               break;

                                               case 8:

                                                               b.doPoints();

                                                               break;

                                               case 9:

                                                               b.doSetProgram();

                                                               break;

                                               case 10:

                                                               b.doSetProgramParameters();

                                                               break;

                                               case 11:

                                                               b.doSetTexture1();

                                                               break;

                                               case 12:

                                                               b.doSetColor();

                                                               break;

                                               case 13:

                                                               b.doSetDepthTestEnabled();

                                                               break;

                                               case 14:

                                                               b.doSetEarlyZPass();

                                                               break;

                                               }

                               }

                               this.batchPtr = 0;

                               this.vertexPtr = 0;

                               this.texPtr = 0;

                               this.pointPtr = 0;

                               this.hasQuadBatchTop = false;

                               this.hasPointBatchTop = false;

                               this.isBatchInEarlyZPass = false;

                               this.curBuffer++;

                               if (this.curBuffer >= MULTI_BUFFERS)

                                               this.curBuffer = 0;

                };

                GLWrap_.prototype.setOpacity = function (op)

                {

                               if (op === this.lastOpacity)

                                               return;

                               if (this.isEarlyZPass)

                                               return;                   // ignore

                               var b = this.pushBatch();

                               b.type = BATCH_SETOPACITY;

                               b.opacityParam = op;

                               this.lastOpacity = op;

                                this.hasQuadBatchTop = false;

                               this.hasPointBatchTop = false;

                };

                GLWrap_.prototype.setTexture = function (tex)

                {

                               if (tex === this.lastTexture0)

                                               return;

;

                               var b = this.pushBatch();

                               b.type = BATCH_SETTEXTURE;

                               b.texParam = tex;

                               this.lastTexture0 = tex;

                               this.hasQuadBatchTop = false;

                               this.hasPointBatchTop = false;

                };

                GLWrap_.prototype.setBlend = function (s, d)

                {

                               if (s === this.lastSrcBlend && d === this.lastDestBlend)

                                               return;

                               if (this.isEarlyZPass)

                                               return;                   // ignore

                               var b = this.pushBatch();

                               b.type = BATCH_SETBLEND;

                               b.startIndex = s;                  // recycle params to save memory

                               b.indexCount = d;

                               this.lastSrcBlend = s;

                               this.lastDestBlend = d;

                               this.hasQuadBatchTop = false;

                               this.hasPointBatchTop = false;

                };

                GLWrap_.prototype.isPremultipliedAlphaBlend = function ()

                {

                               return (this.lastSrcBlend === this.gl.ONE && this.lastDestBlend === this.gl.ONE_MINUS_SRC_ALPHA);

                };

                GLWrap_.prototype.setAlphaBlend = function ()

                {

                               this.setBlend(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA);

                };

                GLWrap_.prototype.setNoPremultiplyAlphaBlend = function ()

                {

                               this.setBlend(this.gl.SRC_ALPHA, this.gl.ONE_MINUS_SRC_ALPHA);

                };

                var LAST_VERTEX = MAX_VERTICES * 2 - 8;

                GLWrap_.prototype.quad = function(tlx, tly, trx, try_, brx, bry, blx, bly)

                {

                               if (this.vertexPtr >= LAST_VERTEX)

                                               this.endBatch();

                               var v = this.vertexPtr;                                        // vertex cursor

                               var t = this.texPtr;

                               var vd = this.vertexData;                  // vertex data array

                               var td = this.texcoordData;                              // texture coord data array

                               var currentZ = this.currentZ;

                               if (this.hasQuadBatchTop)

                               {

                                               this.batch[this.batchPtr - 1].indexCount += 6;

                               }

                               else

                               {

                                               var b = this.pushBatch();

                                               b.type = BATCH_QUAD;

                                               b.startIndex = this.enableFrontToBack ? v: (v / 2) * 3;

                                               b.indexCount = 6;

                                               this.hasQuadBatchTop = true;

                                               this.hasPointBatchTop = false;

                               }

                               if (this.enableFrontToBack)

                               {

                                               vd[v++] = tlx;

                                               vd[v++] = tly;

                                               vd[v++] = currentZ;

                                               vd[v++] = trx;

                                               vd[v++] = try_;

                                               vd[v++] = currentZ;

                                               vd[v++] = brx;

                                               vd[v++] = bry;

                                               vd[v++] = currentZ;

                                               vd[v++] = blx;

                                               vd[v++] = bly;

                                               vd[v++] = currentZ;

                               }

                               else

                               {

                                               vd[v++] = tlx;

                                               vd[v++] = tly;

                                               vd[v++] = trx;

                                               vd[v++] = try_;

                                               vd[v++] = brx;

                                               vd[v++] = bry;

                                               vd[v++] = blx;

                                               vd[v++] = bly;

                               }

                               td[t++] = 0;

                               td[t++] = 0;

                               td[t++] = 1;

                               td[t++] = 0;

                               td[t++] = 1;

                               td[t++] = 1;

                               td[t++] = 0;

                               td[t++] = 1;

                               this.vertexPtr = v;

                               this.texPtr = t;

                };

                GLWrap_.prototype.quadTex = function(tlx, tly, trx, try_, brx, bry, blx, bly, rcTex)

                {

                               if (this.vertexPtr >= LAST_VERTEX)

                                               this.endBatch();

                               var v = this.vertexPtr;                                        // vertex cursor

                               var t = this.texPtr;

                               var vd = this.vertexData;                  // vertex data array

                               var td = this.texcoordData;                              // texture coord data array

                               var currentZ = this.currentZ;

                               if (this.hasQuadBatchTop)

                               {

                                               this.batch[this.batchPtr - 1].indexCount += 6;

                               }

                               else

                               {

                                               var b = this.pushBatch();

                                               b.type = BATCH_QUAD;

                                               b.startIndex = this.enableFrontToBack ? v: (v / 2) * 3;

                                               b.indexCount = 6;

                                               this.hasQuadBatchTop = true;

                                               this.hasPointBatchTop = false;

                               }

                               var rc_left = rcTex.left;

                               var rc_top = rcTex.top;

                               var rc_right = rcTex.right;

                               var rc_bottom = rcTex.bottom;

                               if (this.enableFrontToBack)

                               {

                                               vd[v++] = tlx;

                                               vd[v++] = tly;

                                               vd[v++] = currentZ;

                                               vd[v++] = trx;

                                               vd[v++] = try_;

                                               vd[v++] = currentZ;

                                               vd[v++] = brx;

                                               vd[v++] = bry;

                                               vd[v++] = currentZ;

                                               vd[v++] = blx;

                                               vd[v++] = bly;

                                               vd[v++] = currentZ;

                               }

                               else

                               {

                                               vd[v++] = tlx;

                                               vd[v++] = tly;

                                               vd[v++] = trx;

                                               vd[v++] = try_;

                                               vd[v++] = brx;

                                               vd[v++] = bry;

                                               vd[v++] = blx;

                                               vd[v++] = bly;

                               }

                               td[t++] = rc_left;

                               td[t++] = rc_top;

                               td[t++] = rc_right;

                               td[t++] = rc_top;

                               td[t++] = rc_right;

                               td[t++] = rc_bottom;

                               td[t++] = rc_left;

                               td[t++] = rc_bottom;

                               this.vertexPtr = v;

                               this.texPtr = t;

                };

                GLWrap_.prototype.quadTexUV = function(tlx, tly, trx, try_, brx, bry, blx, bly, tlu, tlv, tru, trv, bru, brv, blu, blv)

                {

                               if (this.vertexPtr >= LAST_VERTEX)

                                               this.endBatch();

                                var v = this.vertexPtr;                                        // vertex cursor

                               var t = this.texPtr;

                               var vd = this.vertexData;                  // vertex data array

                               var td = this.texcoordData;                              // texture coord data array

                               var currentZ = this.currentZ;

                               if (this.hasQuadBatchTop)

                               {

                                               this.batch[this.batchPtr - 1].indexCount += 6;

                               }

                               else

                               {

                                               var b = this.pushBatch();

                                               b.type = BATCH_QUAD;

                                               b.startIndex = this.enableFrontToBack ? v: (v / 2) * 3;

                                               b.indexCount = 6;

                                               this.hasQuadBatchTop = true;

                               }

                               if (this.enableFrontToBack)

                               {

                                               vd[v++] = tlx;

                                               vd[v++] = tly;

                                               vd[v++] = currentZ;

                                               vd[v++] = trx;

                                               vd[v++] = try_;

                                               vd[v++] = currentZ;

                                               vd[v++] = brx;

                                               vd[v++] = bry;

                                               vd[v++] = currentZ;

                                               vd[v++] = blx;

                                               vd[v++] = bly;

                                               vd[v++] = currentZ;

                               }

                               else

                               {

                                               vd[v++] = tlx;

                                               vd[v++] = tly;

                                               vd[v++] = trx;

                                               vd[v++] = try_;

                                               vd[v++] = brx;

                                               vd[v++] = bry;

                                               vd[v++] = blx;

                                               vd[v++] = bly;

                               }

                               td[t++] = tlu;

                               td[t++] = tlv;

                               td[t++] = tru;

                               td[t++] = trv;

                               td[t++] = bru;

                               td[t++] = brv;

                               td[t++] = blu;

                               td[t++] = blv;

                               this.vertexPtr = v;

                               this.texPtr = t;

                };

                GLWrap_.prototype.convexPoly = function(pts)

                {

                               var pts_count = pts.length / 2;

;

                               var tris = pts_count - 2;     // 3 points = 1 tri, 4 points = 2 tris, 5 points = 3 tris etc.

                               var last_tri = tris - 1;

                               var p0x = pts[0];

                               var p0y = pts[1];

                               var i, i2, p1x, p1y, p2x, p2y, p3x, p3y;

                               for (i = 0; i < tris; i += 2)                     // draw 2 triangles at a time

                               {

                                               i2 = i * 2;

                                               p1x = pts[i2 + 2];

                                               p1y = pts[i2 + 3];

                                               p2x = pts[i2 + 4];

                                               p2y = pts[i2 + 5];

                                               if (i === last_tri)

                                               {

                                                               this.quad(p0x, p0y, p1x, p1y, p2x, p2y, p2x, p2y);

                                               }

                                               else

                                               {

                                                               p3x = pts[i2 + 6];

                                                               p3y = pts[i2 + 7];

                                                               this.quad(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y);

                                               }

                               }

                };

                var LAST_POINT = MAX_POINTS - 4;

                GLWrap_.prototype.point = function(x_, y_, size_, opacity_)

                {

                               if (this.pointPtr >= LAST_POINT)

                                               this.endBatch();

                               var p = this.pointPtr;                                          // point cursor

                               var pd = this.pointData;                    // point data array

                               if (this.hasPointBatchTop)

                               {

                                               this.batch[this.batchPtr - 1].indexCount++;

                               }

                               else

                               {

                                               var b = this.pushBatch();

                                               b.type = BATCH_POINTS;

                                               b.startIndex = p;

                                               b.indexCount = 1;

                                               this.hasPointBatchTop = true;

                                               this.hasQuadBatchTop = false;

                               }

                               pd[p++] = x_;

                               pd[p++] = y_;

                               pd[p++] = size_;

                               pd[p++] = opacity_;

                               this.pointPtr = p;

                };

                GLWrap_.prototype.switchProgram = function (progIndex)

                {

                               if (this.lastProgram === progIndex)

                                               return;                                   // no change

                               var shaderProg = this.shaderPrograms[progIndex];

                               if (!shaderProg)

                               {

                                               if (this.lastProgram === 0)

                                                               return;                                                                                                                 // already on default shader

                                               progIndex = 0;

                                               shaderProg = this.shaderPrograms[0];

                               }

                               var b = this.pushBatch();

                               b.type = BATCH_SETPROGRAM;

                               b.startIndex = progIndex;

                               this.lastProgram = progIndex;

                               this.hasQuadBatchTop = false;

                               this.hasPointBatchTop = false;

                };

                GLWrap_.prototype.programUsesDest = function (progIndex)

                {

                               var s = this.shaderPrograms[progIndex];

                               return!!(s.locDestStart || s.locDestEnd);

                };

                GLWrap_.prototype.programUsesCrossSampling = function (progIndex)

                {

                               var s = this.shaderPrograms[progIndex];

                               return!!(s.locDestStart || s.locDestEnd || s.crossSampling);

                };

                GLWrap_.prototype.programPreservesOpaqueness = function (progIndex)

                {

                               return this.shaderPrograms[progIndex].preservesOpaqueness;

                };

                GLWrap_.prototype.programExtendsBox = function (progIndex)

                {

                               var s = this.shaderPrograms[progIndex];

                               return s.extendBoxHorizontal!== 0 || s.extendBoxVertical!== 0;

                };

                GLWrap_.prototype.getProgramBoxExtendHorizontal = function (progIndex)

                {

                               return this.shaderPrograms[progIndex].extendBoxHorizontal;

                };

                GLWrap_.prototype.getProgramBoxExtendVertical = function (progIndex)

                {

                               return this.shaderPrograms[progIndex].extendBoxVertical;

                };

                GLWrap_.prototype.getProgramParameterType = function (progIndex, paramIndex)

                {

                               return this.shaderPrograms[progIndex].parameters[paramIndex][2];

                };

                GLWrap_.prototype.programIsAnimated = function (progIndex)

                {

                               return this.shaderPrograms[progIndex].animated;

                };

                GLWrap_.prototype.setProgramParameters = function (backTex, pixelWidth, pixelHeight, destStartX, destStartY, destEndX, destEndY, layerScale, layerAngle, viewOriginLeft, viewOriginTop, scrollPosX, scrollPosY, seconds, params)

                {

                               var i, len;

                               var s = this.shaderPrograms[this.lastProgram];

                               var b, mat4param, shaderParams;

                               if (s.hasAnyOptionalUniforms || params.length)

                               {

                                               b = this.pushBatch();

                                               b.type = BATCH_SETPROGRAMPARAMETERS;

                                               if (b.mat4param)

                                                               mat4.set(this.matMV, b.mat4param);

                                               else

                                                              b.mat4param = mat4.create();

                                               mat4param = b.mat4param;

                                               mat4param[0] = pixelWidth;

                                               mat4param[1] = pixelHeight;

                                               mat4param[2] = destStartX;

                                               mat4param[3] = destStartY;

                                               mat4param[4] = destEndX;

                                               mat4param[5] = destEndY;

                                               mat4param[6] = layerScale;

                                               mat4param[7] = layerAngle;

                                               mat4param[8] = viewOriginLeft;

                                               mat4param[9] = viewOriginTop;

                                               mat4param[10] = scrollPosX;

                                               mat4param[11] = scrollPosY;

                                               mat4param[12] = seconds;

                                               if (s.locSamplerBack)

                                               {

;

                                                               b.texParam = backTex;

                                               }

                                               else

                                                               b.texParam = null;

                                               if (params.length)

                                               {

                                                               shaderParams = b.shaderParams;

                                                               shaderParams.length = params.length;

                                                               for (i = 0, len = params.length; i < len; i++)

                                                                              shaderParams[i] = params[i];

                                               }

                                               this.hasQuadBatchTop = false;

                                               this.hasPointBatchTop = false;

                               }

                };

                GLWrap_.prototype.clear = function (r, g, b_, a)

                {

                               var b = this.pushBatch();

                               b.type = BATCH_CLEAR;

                               b.startIndex = 0;                                                                // clear all mode

                               if (!b.mat4param)

                                               b.mat4param = mat4.create();

                               b.mat4param[0] = r;

                               b.mat4param[1] = g;

                               b.mat4param[2] = b_;

                               b.mat4param[3] = a;

                               this.hasQuadBatchTop = false;

                               this.hasPointBatchTop = false;

                };

                GLWrap_.prototype.clearRect = function (x, y, w, h)

                {

                               if (w < 0 || h < 0)

                                               return;                                                                                                  // invalid clear area

                               var b = this.pushBatch();

                               b.type = BATCH_CLEAR;

                               b.startIndex = 1;                                                                // clear rect mode

                               if (!b.mat4param)

                                               b.mat4param = mat4.create();

                               b.mat4param[0] = x;

                               b.mat4param[1] = y;

                               b.mat4param[2] = w;

                               b.mat4param[3] = h;

                               this.hasQuadBatchTop = false;

                               this.hasPointBatchTop = false;

                };

                GLWrap_.prototype.clearDepth = function ()

                {

                               var b = this.pushBatch();

                               b.type = BATCH_CLEAR;

                               b.startIndex = 2;                                                                // clear depth mode

                               this.hasQuadBatchTop = false;

                               this.hasPointBatchTop = false;

                };

                GLWrap_.prototype.setEarlyZPass = function (e)

                {

                               if (!this.enableFrontToBack)

                                               return;                   // no depth buffer in use

                               e =!!e;

                               if (this.isEarlyZPass === e)

                                               return;                   // no change

                               var b = this.pushBatch();

                               b.type = BATCH_SETEARLYZMODE;

                               b.startIndex = (e ? 1: 0);

                               this.hasQuadBatchTop = false;

                               this.hasPointBatchTop = false;

                               this.isEarlyZPass = e;

                               this.renderToTex = null;

                               if (this.isEarlyZPass)

                               {

                                               this.switchProgram(2);                      // early Z program

                               }

                                else

                               {

                                               this.switchProgram(0);                      // normal rendering

                               }

                };

                GLWrap_.prototype.setDepthTestEnabled = function (e)

                {

                               if (!this.enableFrontToBack)

                                               return;                   // no depth buffer in use

                               var b = this.pushBatch();

                               b.type = BATCH_SETDEPTHTEST;

                               b.startIndex = (e ? 1: 0);

                               this.hasQuadBatchTop = false;

                               this.hasPointBatchTop = false;

                };

                GLWrap_.prototype.fullscreenQuad = function ()

                {

                               mat4.set(this.lastMV, tempMat4);

                               this.resetModelView();

                               this.updateModelView();

                               var halfw = this.width / 2;

                               var halfh = this.height / 2;

                               this.quad(-halfw, halfh, halfw, halfh, halfw, -halfh, -halfw, -halfh);

                               mat4.set(tempMat4, this.matMV);

                               this.updateModelView();

                };

                GLWrap_.prototype.setColorFillMode = function (r_, g_, b_, a_)

                {

                               this.switchProgram(3);

                               var b = this.pushBatch();

                               b.type = BATCH_SETCOLOR;

                               if (!b.mat4param)

                                               b.mat4param = mat4.create();

                               b.mat4param[0] = r_;

                               b.mat4param[1] = g_;

                               b.mat4param[2] = b_;

                               b.mat4param[3] = a_;

                               this.hasQuadBatchTop = false;

                               this.hasPointBatchTop = false;

                };

                GLWrap_.prototype.setTextureFillMode = function ()

                {

;

                               this.switchProgram(0);

                };

                GLWrap_.prototype.restoreEarlyZMode = function ()

                {

;

                               this.switchProgram(2);

                };

                GLWrap_.prototype.present = function ()

                {

                               this.endBatch();

                               this.gl.flush();

                               /*

                               if (debugBatch)

                               {

;

                                               debugBatch = false;

                               }

                               */

                }; .getObjectRefTable = function () { return [

                cr.plugins_.Button,

                cr.plugins_.Mouse,

                cr.plugins_.Keyboard,

                cr.plugins_.Sprite,

                cr.plugins_.TextBox,

                cr.behaviors.solid,

                cr.plugins_.Mouse.prototype.cnds.OnObjectClicked,

                cr.system_object.prototype.acts.SetLayerVisible,

                cr.plugins_.Button.prototype.cnds.OnClicked,

                cr.system_object.prototype.acts.SetVar,

                cr.plugins_.TextBox.prototype.cnds.CompareText,

                cr.plugins_.TextBox.prototype.acts.SetCSSStyle,

                cr.plugins_.Sprite.prototype.acts.SetAnim,

                cr.plugins_.Sprite.prototype.acts.SetVisible,

                cr.plugins_.Sprite.prototype.acts.StartAnim,

                cr.system_object.prototype.cnds.CompareVar,

                cr.system_object.prototype.acts.GoToLayout

];};

Похожие работы на - Виртуальная модель предметной области дисциплины 'Представление знаний в информационных системах'

 

Не нашли материал для своей работы?
Поможем написать уникальную работу
Без плагиата!