package alternativa.engine3d.loaders { import alternativa.engine3d.*; import alternativa.types.Map; import alternativa.utils.ColorUtils; import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.Loader; import flash.events.ErrorEvent; import flash.events.Event; import flash.events.EventDispatcher; import flash.events.IOErrorEvent; import flash.events.SecurityErrorEvent; import flash.geom.Point; import flash.net.URLLoader; import flash.net.URLRequest; import flash.system.LoaderContext; use namespace alternativa3d; /** * @private * Загрузчик библиотеки материалов из файлов в формате MTL material format (Lightwave, OBJ). *

* На данный момент обеспечивается загрузка цвета, прозрачности и диффузной текстуры материала. */ internal class LoaderMTL extends EventDispatcher { private static const COMMENT_CHAR:String = "#"; private static const CMD_NEW_MATERIAL:String = "newmtl"; private static const CMD_DIFFUSE_REFLECTIVITY:String = "Kd"; private static const CMD_DISSOLVE:String = "d"; private static const CMD_MAP_DIFFUSE:String = "map_Kd"; private static const REGEXP_TRIM:RegExp = /^\s*(.*?)\s*$/; private static const REGEXP_SPLIT_FILE:RegExp = /\r*\n/; private static const REGEXP_SPLIT_LINE:RegExp = /\s+/; // Загрузчик файла MTL private var fileLoader:URLLoader; // Загрузчик файлов текстур private var bitmapLoader:Loader; // Контекст загрузки для bitmapLoader private var loaderContext:LoaderContext; // Базовый URL файла MTL private var baseUrl:String; // Библиотека загруженных материалов private var _library:Map; // Список материалов, имеющих диффузные текстуры private var diffuseMaps:Map; // Имя текущего материала private var materialName:String; // параметры текущего материала private var currentMaterialInfo:MaterialInfo = new MaterialInfo(); alternativa3d static var stubBitmapData:BitmapData; /** * Создаёт новый экземпляр класса. */ public function LoaderMTL() { } /** * Прекращение текущей загрузки. */ public function close():void { try { fileLoader.close(); } catch (e:Error) { } } /** * Библиотека материалов. Ключами являются наименования материалов, значениями -- объекты, наследники класса * alternativa.engine3d.loaders.MaterialInfo. * @see alternativa.engine3d.loaders.MaterialInfo */ public function get library():Map { return _library; } /** * Метод выполняет загрузку файла материалов, разбор его содержимого, загрузку текстур при необходимости и * формирование библиотеки материалов. После окончания работы метода посылается сообщение * Event.COMPLETE и становится доступна библиотека материалов через свойство library. *

* При возникновении ошибок, связанных с вводом-выводом или с безопасностью, посылаются сообщения IOErrorEvent.IO_ERROR и * SecurityErrorEvent.SECURITY_ERROR соответственно. *

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

* @param url URL MTL-файла * @param loaderContext LoaderContext для загрузки файлов текстур * * @see #library */ public function load(url:String, loaderContext:LoaderContext = null):void { this.loaderContext = loaderContext; baseUrl = url.substring(0, url.lastIndexOf("/") + 1); if (fileLoader == null) { fileLoader = new URLLoader(); fileLoader.addEventListener(Event.COMPLETE, parseMTLFile); fileLoader.addEventListener(IOErrorEvent.IO_ERROR, onError); fileLoader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError); bitmapLoader = new Loader(); bitmapLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, onBitmapLoadComplete); bitmapLoader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, onBitmapLoadComplete); bitmapLoader.contentLoaderInfo.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onBitmapLoadComplete); } try { fileLoader.close(); bitmapLoader.close(); } catch (e:Error) { // Пропуск ошибки при попытке закрытия неактивных загрузчиков } fileLoader.load(new URLRequest(url)); } /** * Разбор содержимого загруженного файла материалов. */ private function parseMTLFile(e:Event = null):void { var lines:Array = fileLoader.data.split(REGEXP_SPLIT_FILE); _library = new Map(); diffuseMaps = new Map(); for each (var line:String in lines) { parseLine(line); } defineMaterial(); if (diffuseMaps.isEmpty()) { // Текстур нет, загрузка окончена dispatchEvent(new Event(Event.COMPLETE)); } else { // Загрузка файлов текстур loadNextBitmap(); } } /** * Разбор строки файла. * * @param line строка файла */ private function parseLine(line:String):void { line = line.replace(REGEXP_TRIM,"$1") if (line.length == 0 || line.charAt(0) == COMMENT_CHAR) { return; } var parts:Array = line.split(REGEXP_SPLIT_LINE); switch (parts[0]) { case CMD_NEW_MATERIAL: defineMaterial(parts); break; case CMD_DIFFUSE_REFLECTIVITY: readDiffuseReflectivity(parts); break; case CMD_DISSOLVE: readAlpha(parts); break; case CMD_MAP_DIFFUSE: parseDiffuseMapLine(parts); break; } } /** * Определение нового материала. */ private function defineMaterial(parts:Array = null):void { if (materialName != null) { _library[materialName] = currentMaterialInfo; } if (parts != null) { materialName = parts[1]; currentMaterialInfo = new MaterialInfo(); } } /** * Чтение коэффициентов диффузного отражения. Считываются только коэффициенты, заданные в формате r g b. Для текущей * версии движка данные коэффициенты преобразуются в цвет материала. */ private function readDiffuseReflectivity(parts:Array):void { var r:Number = Number(parts[1]); // Проверка, заданы ли коэффициенты в виде r g b if (!isNaN(r)) { var g:Number = Number(parts[2]); var b:Number = Number(parts[3]); currentMaterialInfo.color = ColorUtils.rgb(255 * r, 255 * g, 255 * b); } } /** * Чтение коэффициента непрозрачности. Считывается только коэффициент, заданный числом * (не поддерживается параметр -halo). */ private function readAlpha(parts:Array):void { var alpha:Number = Number(parts[1]); if (!isNaN(alpha)) { currentMaterialInfo.alpha = alpha; } } /** * Разбор строки, задающей текстурную карту для диффузного отражения. */ private function parseDiffuseMapLine(parts:Array):void { var info:MTLTextureMapInfo = MTLTextureMapInfo.parse(parts); diffuseMaps[materialName] = info; } /** * Загрузка файла следующей текстуры. */ private function loadNextBitmap():void { // Установка имени текущего текстурного материала, для которого выполняется загрузка текстуры for (materialName in diffuseMaps) { break; } bitmapLoader.load(new URLRequest(baseUrl + diffuseMaps[materialName].fileName), loaderContext); } /** * */ private function createStubBitmap():void { if (stubBitmapData == null) { var size:uint = 10; stubBitmapData = new BitmapData(size, size, false, 0); for (var i:uint = 0; i < size; i++) { for (var j:uint = 0; j < size; j+=2) { stubBitmapData.setPixel((i % 2) ? j : (j+1), i, 0xFF00FF); } } } } /** * Обработка результата загрузки файла текстуры. */ private function onBitmapLoadComplete(e:Event):void { var bmd:BitmapData; if (e is ErrorEvent) { if (stubBitmapData == null) { createStubBitmap(); } bmd = stubBitmapData; } else { bmd = Bitmap(bitmapLoader.content).bitmapData; } var mtlInfo:MTLTextureMapInfo = diffuseMaps[materialName]; delete diffuseMaps[materialName]; var info:MaterialInfo = _library[materialName]; info.bitmapData = bmd; info.repeat = mtlInfo.repeat; info.mapOffset = new Point(mtlInfo.offsetU, mtlInfo.offsetV); info.mapSize = new Point(mtlInfo.sizeU, mtlInfo.sizeV); info.textureFileName = mtlInfo.fileName; if (diffuseMaps.isEmpty()) { dispatchEvent(new Event(Event.COMPLETE)); } else { loadNextBitmap(); } } /** * * @param e */ private function onError(e:IOErrorEvent):void { dispatchEvent(e); } } }