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); } } }