public static void ConvertAiSceneFromModels(List <Model> models, string outputFileName, TextureDatabase textureDatabase = null) { SceneUtilities.Export(ConvertAiSceneFromModels(models, textureDatabase), outputFileName); var texturesOutputDirectory = Path.GetDirectoryName(outputFileName); foreach (var model in models.Where(x => x.TextureSet != null)) { TextureUtilities.SaveTextures(model.TextureSet, texturesOutputDirectory); } }
private Bitmap _loadTexture(ITexmap texMap) { IBitmapTex texture = _getBitmapTex(texMap); if (texture == null) { return(null); } return(TextureUtilities.LoadTexture(texture.Map.FullFilePath, this)); }
public static void ConvertAiSceneFromObjectSets(List <ObjectSet> objectSets, string outputFileName, TextureDatabase textureDatabase = null) { SceneUtilities.Export(ConvertAiSceneFromObjectSets(objectSets, textureDatabase), outputFileName); var texturesOutputDirectory = Path.GetDirectoryName(outputFileName); foreach (var objectSet in objectSets.Where(x => x.TextureSet != null)) { TextureUtilities.SaveTextures(objectSet.TextureSet, texturesOutputDirectory); } }
public static void ConvertAiSceneFromObjectSet(ObjectSet objectSet, string outputFileName, TextureDatabase textureDatabase = null, bool appendTags = false) { SceneUtilities.Export(ConvertAiSceneFromObjectSet(objectSet, textureDatabase, appendTags), outputFileName); if (objectSet.TextureSet != null) { string texturesOutputDirectory = Path.GetDirectoryName(outputFileName); TextureUtilities.SaveTextures(objectSet.TextureSet, texturesOutputDirectory); } }
public override void DrawTimelineGui(Rect rect) { #if UNITY_EDITOR if (EventName != null && name != EventName) { name = EventName; UnityEditor.EditorUtility.SetDirty(this); } #endif var guiStyle = new GUIStyle(GUI.skin.box); guiStyle.normal.background = TextureUtilities.MakeTexture((int)rect.width, (int)rect.height, new Color(0.54f, 1f, 0.58f), new RectOffset(5, 5, 5, 5), new Color(0f, 0.35f, 0.1f)); GUI.Box(rect, string.Format("{0:F3}:{1}", StartTime, EventName), guiStyle); }
private void UpdateState(List <Texture2D> source, List <Texture2D> copyHolder) { if (source.Count == 0) { return; } copyHolder.Insert(0, TextureUtilities.CopyTexture(Painter.CanvasTexture)); TextureUtilities.CopyTexture(source.First(), Painter.CanvasTexture); source.DestroyAndRemove(0); CheckForMaxDepthExceed(); }
public void RegisterState() { if (Painter == null) { return; } _undoTextures.Insert(0, TextureUtilities.CopyTexture(Painter.CanvasTexture)); _redoTextures.DestroyAll(); _redoTextures.Clear(); CheckForMaxDepthExceed(); }
public static Ai.Scene ConvertAiSceneFromObjectSets(List <ObjectSet> objectSets, TextureDatabase textureDatabase = null) { var aiScene = new Ai.Scene(); aiScene.RootNode = new Ai.Node("RootNode"); foreach (var objectSet in objectSets.Where(x => x.TextureSet != null)) { TextureUtilities.RenameTextures(objectSet.TextureSet, textureDatabase); } foreach (var objectSet in objectSets) { foreach (var obj in objectSet.Objects) { ConvertAiMaterialsFromMaterials(aiScene, obj.Materials, objectSet.TextureSet); } } var bones = new List <BoneInfo>(); var boneParentMap = new Dictionary <string, string>(); foreach (var objectSet in objectSets) { foreach (var obj in objectSet.Objects.Where(x => x.Skin != null)) { foreach (var boneInfo in obj.Skin.Bones) { if (!boneParentMap.ContainsKey(boneInfo.Name)) { var parentBone = obj.Skin.Bones.FirstOrDefault(x => x.Id == boneInfo.ParentId); bones.Add(boneInfo); boneParentMap.Add(boneInfo.Name, parentBone?.Name); } } } } ConvertAiNodesFromBones(aiScene, bones, boneParentMap); foreach (var objectSet in objectSets) { ConvertAiNodesFromObjects(aiScene, objectSet.Objects); } return(aiScene); }
public static Ai.Scene ConvertAiSceneFromModels(List <Model> models, TextureDatabase textureDatabase = null) { var aiScene = new Ai.Scene(); aiScene.RootNode = new Ai.Node("RootNode"); foreach (var model in models.Where(x => x.TextureSet != null)) { TextureUtilities.RenameTextures(model.TextureSet, textureDatabase); } foreach (var model in models) { foreach (var mesh in model.Meshes) { ConvertAiMaterialsFromMaterials(aiScene, mesh.Materials, model.TextureSet); } } var bones = new List <Bone>(); var boneParentMap = new Dictionary <string, string>(); foreach (var model in models) { foreach (var mesh in model.Meshes.Where(x => x.Skin != null)) { foreach (var bone in mesh.Skin.Bones) { if (!boneParentMap.ContainsKey(bone.Name)) { var parentBone = mesh.Skin.Bones.FirstOrDefault(x => x.Id == bone.ParentId); bones.Add(bone); boneParentMap.Add(bone.Name, parentBone?.Name); } } } } ConvertAiNodesFromBones(aiScene, bones, boneParentMap); foreach (var model in models) { ConvertAiNodesFromMeshes(aiScene, model.Meshes); } return(aiScene); }
private static Ai.TextureSlot ConvertTextureSlotFromTextureId(int textureId, Ai.TextureType type, TextureSet textureList) { if (textureId == -1 || textureList == null) { return(default(Ai.TextureSlot)); } var texture = textureList.Textures.FirstOrDefault(x => x.Id == textureId); if (texture != null) { return(new Ai.TextureSlot(TextureUtilities.GetFileName(texture), type, 0, Ai.TextureMapping.FromUV, 0, 0, Ai.TextureOperation.Add, Ai.TextureWrapMode.Wrap, Ai.TextureWrapMode.Wrap, 0)); } return(default(Ai.TextureSlot)); }
void Start() { //Check if already saved a texture in disk with key "myTexture" if (TextureUtilities.IsTextureSaved("myTexture")) { //Load saved texture imageTextureCopy = TextureUtilities.Load("myTexture"); //Assign loaded texture to your sprite imageSprite.sprite = TextureUtilities.GetSpriteFromTexture(imageTextureCopy); } //Get texture copy in order to manipulate its data and pixels. //you can't access texture data directly, it will throw an error : (Texture is not readable) imageTextureCopy = TextureUtilities.GetTextureCopy(imageSprite.sprite.texture); }
public static CNMutableContact ToCNMutableContact(this Contact contact) { if (contact == null) { return(null); } CNMutableContact nativeContact = new CNMutableContact(); if (contact.FirstName != null) { nativeContact.GivenName = NSString.StringWithUTF8String(contact.FirstName); } if (contact.MiddleName != null) { nativeContact.MiddleName = NSString.StringWithUTF8String(contact.MiddleName); } if (contact.LastName != null) { nativeContact.FamilyName = NSString.StringWithUTF8String(contact.LastName); } if (contact.Company != null) { nativeContact.OrganizationName = NSString.StringWithUTF8String(contact.Company); } if (contact.Birthday != null) { nativeContact.Birthday = contact.Birthday.Value.ToNSDateComponents(); } nativeContact.EmailAddresses = ToCNCotactEmails(contact.Emails); nativeContact.PhoneNumbers = ToCNContactPhoneNumbers(contact.PhoneNumbers); if (contact.Photo != null) { byte[] rawData = TextureUtilities.EncodeAsByteArray(contact.Photo, ImageFormat.PNG); nativeContact.ImageData = NSData.DataWithBytes(rawData, (uint)rawData.Length); } return(nativeContact); }
private GLTFBufferView WriteImageToGltfBuffer(GLTF gltf, GLTFImage gltfImage, string imageSourcePath = null, Bitmap imageBitmap = null, long textureQuality = 100) { byte[] imageBytes = null; if (imageBitmap != null) { // try our best to get extension - default will be png which is the looseless format. var extension = gltfImage.FileExtension ?? (gltfImage.uri != null ? Path.GetExtension(gltfImage.uri) : null); var outputFormat = extension != null?TextureUtilities.GetImageFormat(gltfImage.FileExtension) : ImageFormat.Png; using (MemoryStream m = new MemoryStream()) { // this use the SAME method for GLTF TextureUtilities.SaveBitmap(m, imageBitmap, outputFormat, textureQuality); imageBytes = m.ToArray(); } } else { imageBytes = File.ReadAllBytes(imageSourcePath); } // Chunk must be padded with trailing zeros (0x00) to satisfy alignment requirements imageBytes = padChunk(imageBytes, 4, 0x00); // BufferView - Image var buffer = gltf.buffer; var imageBufferView = new GLTFBufferView { name = "bufferViewImage", buffer = buffer.index, Buffer = buffer, byteOffset = buffer.byteLength }; imageBufferView.index = gltf.BufferViewsList.Count; gltf.BufferViewsList.Add(imageBufferView); imageBufferView.bytesList.AddRange(imageBytes); imageBufferView.byteLength += imageBytes.Length; imageBufferView.Buffer.byteLength += imageBytes.Length; imageBufferView.Buffer.bytesList.AddRange(imageBufferView.bytesList); return(imageBufferView); }
public void SaveImage(Texture2D image, string name, ImageFormat format = ImageFormat.JPG, Action <string> callback = null) { if (callback == null) { Debug.LogError(NullCallbackMessage); return; } if (image == null) { callback(NullImageMessage); return; } if (nativeGallery == null) { callback(NullNativeGalleryMessage); return; } nativeGallery.Call(NativeSaveImageName, TextureUtilities.Encode(image, format), name, (int)format, new AndroidSaveImageProxy(callback), false); // false = saveToInternal }
private GLTFTextureInfo ExportEmissiveTexture(BabylonStandardMaterial babylonMaterial, GLTF gltf, float[] defaultEmissive, float[] defaultDiffuse) { // Use one as a reference for UVs parameters var babylonTexture = babylonMaterial.emissiveTexture != null ? babylonMaterial.emissiveTexture : babylonMaterial.diffuseTexture; if (babylonTexture == null) { return(null); } // Anticipate if a black texture is going to be export if (babylonMaterial.emissiveTexture == null && defaultEmissive.IsAlmostEqualTo(new float[] { 0, 0, 0 }, 0)) { return(null); } // Check if the texture has already been exported if (GetRegisteredEmissive(babylonMaterial, defaultDiffuse, defaultEmissive) != null) { return(GetRegisteredEmissive(babylonMaterial, defaultDiffuse, defaultEmissive)); } Bitmap emissivePremultipliedBitmap = null; if (exportParameters.writeTextures) { // Emissive Bitmap emissiveBitmap = null; if (babylonMaterial.emissiveTexture != null) { emissiveBitmap = TextureUtilities.LoadTexture(babylonMaterial.emissiveTexture.originalPath, logger); } // Diffuse Bitmap diffuseBitmap = null; if (babylonMaterial.diffuseTexture != null) { diffuseBitmap = TextureUtilities.LoadTexture(babylonMaterial.diffuseTexture.originalPath, logger); } if (emissiveBitmap != null || diffuseBitmap != null) { // Retreive dimensions int width = 0; int height = 0; var haveSameDimensions = TextureUtilities.GetMinimalBitmapDimensions(out width, out height, emissiveBitmap, diffuseBitmap); if (!haveSameDimensions) { logger.RaiseError("Emissive and diffuse maps should have same dimensions", 2); } // Create pre-multiplied emissive map emissivePremultipliedBitmap = new Bitmap(width, height); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { var _emissive = emissiveBitmap != null?emissiveBitmap.GetPixel(x, y).toArrayRGB().Multiply(1f / 255.0f) : defaultEmissive; var _diffuse = diffuseBitmap != null?diffuseBitmap.GetPixel(x, y).toArrayRGB().Multiply(1f / 255.0f) : defaultDiffuse; var emissivePremultiplied = _emissive.Multiply(_diffuse); Color colorEmissivePremultiplied = Color.FromArgb( (int)(emissivePremultiplied[0] * 255), (int)(emissivePremultiplied[1] * 255), (int)(emissivePremultiplied[2] * 255) ); emissivePremultipliedBitmap.SetPixel(x, y, colorEmissivePremultiplied); } } } } var emissiveTextureInfo = ExportBitmapTexture(gltf, babylonTexture, emissivePremultipliedBitmap); // Register the texture for optimisation RegisterEmissive(emissiveTextureInfo, babylonMaterial, defaultDiffuse, defaultEmissive); return(emissiveTextureInfo); }
private BabylonTexture ExportTexture(ITexmap texMap, BabylonScene babylonScene, float amount = 1.0f, bool allowCube = false, bool forceAlpha = false) { IBitmapTex texture = _getBitmapTex(texMap, false); if (texture == null) { float specialAmount; var specialTexMap = _getSpecialTexmap(texMap, out specialAmount); texture = _getBitmapTex(specialTexMap, false); amount *= specialAmount; } if (texture == null) { return(null); } var sourcePath = texture.Map.FullFilePath; if (sourcePath == null || sourcePath == "") { RaiseWarning("Texture path is missing.", 2); return(null); } RaiseMessage("Export texture named: " + Path.GetFileName(sourcePath), 2); var validImageFormat = TextureUtilities.GetValidImageFormat(Path.GetExtension(sourcePath)); if (validImageFormat == null) { // Image format is not supported by the exporter RaiseWarning(string.Format("Format of texture {0} is not supported by the exporter. Consider using a standard image format like jpg or png.", Path.GetFileName(sourcePath)), 3); return(null); } var textureID = texture.GetGuid().ToString(); if (textureMap.ContainsKey(textureID)) { return(textureMap[textureID]); } else { var babylonTexture = new BabylonTexture(textureID) { name = Path.GetFileNameWithoutExtension(texture.MapName) + "." + validImageFormat }; RaiseMessage($"texture id = {babylonTexture.Id}", 2); // Level babylonTexture.level = amount; // Alpha if (forceAlpha) { babylonTexture.hasAlpha = true; babylonTexture.getAlphaFromRGB = (texture.AlphaSource == 2) || (texture.AlphaSource == 3); // 'RGB intensity' or 'None (Opaque)' } else { babylonTexture.hasAlpha = (texture.AlphaSource != 3); // Not 'None (Opaque)' babylonTexture.getAlphaFromRGB = (texture.AlphaSource == 2); // 'RGB intensity' } // UVs var uvGen = _exportUV(texture.UVGen, babylonTexture); // Animations var animations = new List <BabylonAnimation>(); ExportFloatAnimation("uOffset", animations, key => new[] { uvGen.GetUOffs(key) }); ExportFloatAnimation("vOffset", animations, key => new[] { -uvGen.GetVOffs(key) }); ExportFloatAnimation("uScale", animations, key => new[] { uvGen.GetUScl(key) }); ExportFloatAnimation("vScale", animations, key => new[] { uvGen.GetVScl(key) }); ExportFloatAnimation("uAng", animations, key => new[] { uvGen.GetUAng(key) }); ExportFloatAnimation("vAng", animations, key => new[] { uvGen.GetVAng(key) }); ExportFloatAnimation("wAng", animations, key => new[] { uvGen.GetWAng(key) }); babylonTexture.animations = animations.ToArray(); // Copy texture to output if (isBabylonExported) { var destPath = Path.Combine(babylonScene.OutputPath, babylonTexture.name); TextureUtilities.CopyTexture(sourcePath, destPath, exportParameters.txtQuality, this); // Is cube _exportIsCube(Path.Combine(babylonScene.OutputPath, babylonTexture.name), babylonTexture, allowCube); } else { babylonTexture.isCube = false; } babylonTexture.originalPath = sourcePath; return(babylonTexture); } }
private BabylonTexture ExportSpecularTexture(IIGameMaterial materialNode, float[] specularColor, BabylonScene babylonScene) { ITexmap specularColorTexMap = _getTexMap(materialNode, 2); ITexmap specularLevelTexMap = _getTexMap(materialNode, 3); // --- Babylon texture --- var specularColorTexture = _getBitmapTex(specularColorTexMap); var specularLevelTexture = _getBitmapTex(specularLevelTexMap); if (specularLevelTexture == null) { // Copy specular color image // Assume specular color texture is already pre-multiplied by a global specular level value // So do not use global specular level return(ExportTexture(specularColorTexture, babylonScene)); } // Use one as a reference for UVs parameters var texture = specularColorTexture != null ? specularColorTexture : specularLevelTexture; if (texture == null) { return(null); } RaiseMessage("Multiply specular color and level textures", 2); string nameText = null; nameText = (specularColorTexture != null ? Path.GetFileNameWithoutExtension(specularColorTexture.Map.FullFilePath) : TextureUtilities.ColorToStringName(specularColor)) + Path.GetFileNameWithoutExtension(specularLevelTexture.Map.FullFilePath) + "_specularColor"; var textureID = texture.GetGuid().ToString(); if (textureMap.ContainsKey(textureID)) { return(textureMap[textureID]); } else { var babylonTexture = new BabylonTexture(textureID) { name = nameText + ".jpg" // TODO - unsafe name, may conflict with another texture name }; // Level babylonTexture.level = 1.0f; // UVs var uvGen = _exportUV(texture.UVGen, babylonTexture); // Is cube _exportIsCube(texture.Map.FullFilePath, babylonTexture, false); // --- Multiply specular color and level maps --- // Alpha babylonTexture.hasAlpha = false; babylonTexture.getAlphaFromRGB = false; if (exportParameters.writeTextures) { // Load bitmaps var specularColorBitmap = _loadTexture(specularColorTexMap); var specularLevelBitmap = _loadTexture(specularLevelTexMap); if (specularLevelBitmap == null) { // Copy specular color image RaiseError("Failed to load specular level texture. Specular color is exported alone.", 3); return(ExportTexture(specularColorTexture, babylonScene)); } // Retreive dimensions int width = 0; int height = 0; var haveSameDimensions = TextureUtilities.GetMinimalBitmapDimensions(out width, out height, specularColorBitmap, specularLevelBitmap); if (!haveSameDimensions) { RaiseError("Specular color and specular level maps should have same dimensions", 3); } // Create pre-multiplied specular color map var _specularColor = Color.FromArgb( (int)(specularColor[0] * 255), (int)(specularColor[1] * 255), (int)(specularColor[2] * 255)); Bitmap specularColorPreMultipliedBitmap = new Bitmap(width, height); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { var specularColorAtPixel = specularColorBitmap != null?specularColorBitmap.GetPixel(x, y) : _specularColor; var specularLevelAtPixel = specularLevelBitmap.GetPixel(x, y); var specularColorPreMultipliedAtPixel = specularColorAtPixel.multiply(specularLevelAtPixel); specularColorPreMultipliedBitmap.SetPixel(x, y, specularColorPreMultipliedAtPixel); } } // Write bitmap if (isBabylonExported) { RaiseMessage($"Texture | write image '{babylonTexture.name}'", 3); TextureUtilities.SaveBitmap(specularColorPreMultipliedBitmap, babylonScene.OutputPath, babylonTexture.name, ImageFormat.Jpeg, exportParameters.txtQuality, this); } else { // Store created bitmap for further use in gltf export babylonTexture.bitmap = specularColorPreMultipliedBitmap; } } textureMap.Add(babylonTexture.Id, babylonTexture); return(babylonTexture); } }
private BabylonTexture ExportORMTexture(ITexmap ambientOcclusionTexMap, ITexmap roughnessTexMap, ITexmap metallicTexMap, float metallic, float roughness, BabylonScene babylonScene, bool invertRoughness) { // --- Babylon texture --- var metallicTexture = _getBitmapTex(metallicTexMap); var roughnessTexture = _getBitmapTex(roughnessTexMap); var ambientOcclusionTexture = _getBitmapTex(ambientOcclusionTexMap); // Use metallic or roughness texture as a reference for UVs parameters var texture = metallicTexture != null ? metallicTexture : roughnessTexture; if (texture == null) { return(null); } RaiseMessage("Export ORM texture", 2); var textureID = texture.GetGuid().ToString(); if (textureMap.ContainsKey(textureID)) { return(textureMap[textureID]); } else { var babylonTexture = new BabylonTexture(textureID) { name = (ambientOcclusionTexMap != null ? Path.GetFileNameWithoutExtension(ambientOcclusionTexture.Map.FileName) : "") + (roughnessTexMap != null ? Path.GetFileNameWithoutExtension(roughnessTexture.Map.FileName) : ("" + (int)(roughness * 255))) + (metallicTexMap != null ? Path.GetFileNameWithoutExtension(metallicTexture.Map.FileName) : ("" + (int)(metallic * 255))) + ".jpg" // TODO - unsafe name, may conflict with another texture name }; // UVs var uvGen = _exportUV(texture.UVGen, babylonTexture); // Is cube _exportIsCube(texture.Map.FullFilePath, babylonTexture, false); // --- Merge metallic and roughness maps --- if (!isTextureOk(metallicTexMap) && !isTextureOk(roughnessTexMap)) { return(null); } if (exportParameters.writeTextures) { // Load bitmaps var metallicBitmap = _loadTexture(metallicTexMap); var roughnessBitmap = _loadTexture(roughnessTexMap); var ambientOcclusionBitmap = _loadTexture(ambientOcclusionTexMap); // Retreive dimensions int width = 0; int height = 0; var haveSameDimensions = TextureUtilities.GetMinimalBitmapDimensions(out width, out height, metallicBitmap, roughnessBitmap, ambientOcclusionBitmap); if (!haveSameDimensions) { RaiseError((ambientOcclusionBitmap != null ? "Occlusion, roughness and metallic " : "Metallic and roughness") + " maps should have same dimensions", 3); } // Create ORM map Bitmap ormBitmap = new Bitmap(width, height); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { int _occlusion = ambientOcclusionBitmap != null?ambientOcclusionBitmap.GetPixel(x, y).R : 0; int _roughness = roughnessBitmap != null ? (invertRoughness ? 255 - roughnessBitmap.GetPixel(x, y).G : roughnessBitmap.GetPixel(x, y).G) : (int)(roughness * 255.0f); int _metallic = metallicBitmap != null?metallicBitmap.GetPixel(x, y).B : (int)(metallic * 255.0f); // The occlusion values are sampled from the R channel. // The roughness values are sampled from the G channel. // The metalness values are sampled from the B channel. Color colorMetallicRoughness = Color.FromArgb(_occlusion, _roughness, _metallic); ormBitmap.SetPixel(x, y, colorMetallicRoughness); } } // Write bitmap if (isBabylonExported) { RaiseMessage($"Texture | write image '{babylonTexture.name}'", 3); TextureUtilities.SaveBitmap(ormBitmap, babylonScene.OutputPath, babylonTexture.name, ImageFormat.Jpeg, exportParameters.txtQuality, this); } else { // Store created bitmap for further use in gltf export babylonTexture.bitmap = ormBitmap; } } textureMap[babylonTexture.Id] = babylonTexture; return(babylonTexture); } }
/// <returns></returns> private BabylonTexture ExportBaseColorAlphaTexture(ITexmap baseColorTexMap, ITexmap alphaTexMap, float[] baseColor, float alpha, BabylonScene babylonScene, string materialName, bool isOpacity = false) { // --- Babylon texture --- var baseColorTexture = _getBitmapTex(baseColorTexMap); var alphaTexture = _getBitmapTex(alphaTexMap); var texture = baseColorTexture != null ? baseColorTexture : alphaTexture; if (texture == null) { return(null); } var baseColorTextureMapExtension = Path.GetExtension(baseColorTexture.Map.FullFilePath).ToLower(); if (alphaTexture == null && baseColorTexture != null && alpha == 1) { if (baseColorTexture.AlphaSource == 0 && (baseColorTextureMapExtension == ".tif" || baseColorTextureMapExtension == ".tiff")) { RaiseWarning($"Diffuse texture named {baseColorTexture.Map.FullFilePath} is a .tif file and its Alpha Source is 'Image Alpha' by default.", 3); RaiseWarning($"If you don't want material to be in BLEND mode, set diffuse texture Alpha Source to 'None (Opaque)'", 3); } if (baseColorTexture.AlphaSource == 3 && // 'None (Opaque)' baseColorTextureMapExtension == ".jpg" || baseColorTextureMapExtension == ".jpeg" || baseColorTextureMapExtension == ".bmp" || baseColorTextureMapExtension == ".png") { // Copy base color image return(ExportTexture(baseColorTexture, babylonScene)); } } // Use one as a reference for UVs parameters RaiseMessage("Export baseColor+Alpha texture", 2); string nameText = null; nameText = (baseColorTexture != null ? Path.GetFileNameWithoutExtension(baseColorTexture.Map.FullFilePath) : TextureUtilities.ColorToStringName(baseColor)); var textureID = texture.GetGuid().ToString(); if (textureMap.ContainsKey(textureID)) { return(textureMap[textureID]); } else { var babylonTexture = new BabylonTexture(textureID) { name = nameText // TODO - unsafe name, may conflict with another texture name }; // Level babylonTexture.level = 1.0f; // UVs var uvGen = _exportUV(texture.UVGen, babylonTexture); // Is cube _exportIsCube(texture.Map.FullFilePath, babylonTexture, false); // --- Merge baseColor and alpha maps --- var hasBaseColor = isTextureOk(baseColorTexMap); var hasAlpha = isTextureOk(alphaTexMap); // Alpha // If the texture file format does not traditionally support an alpha channel, export the base texture as opaque if (baseColorTextureMapExtension == ".jpg" || baseColorTextureMapExtension == ".jpeg" || baseColorTextureMapExtension == ".bmp") { babylonTexture.hasAlpha = false; } else { babylonTexture.hasAlpha = isTextureOk(alphaTexMap) || (isTextureOk(baseColorTexMap) && baseColorTexture.AlphaSource == 0) || alpha < 1.0f; } babylonTexture.getAlphaFromRGB = false; if ((!isTextureOk(alphaTexMap) && alpha == 1.0f && (isTextureOk(baseColorTexMap) && baseColorTexture.AlphaSource == 0)) && (baseColorTextureMapExtension == ".tif" || baseColorTextureMapExtension == ".tiff")) { RaiseWarning($"Diffuse texture named {baseColorTexture.Map.FullFilePath} is a .tif file and its Alpha Source is 'Image Alpha' by default.", 3); RaiseWarning($"If you don't want material to be in BLEND mode, set diffuse texture Alpha Source to 'None (Opaque)'", 3); } if (!hasBaseColor && !hasAlpha) { return(null); } // Set image format ImageFormat imageFormat = babylonTexture.hasAlpha ? ImageFormat.Png : ImageFormat.Jpeg; babylonTexture.name += imageFormat == ImageFormat.Png ? ".png" : ".jpg"; // --- Merge baseColor and alpha maps --- if (exportParameters.writeTextures) { // Load bitmaps var baseColorBitmap = _loadTexture(baseColorTexMap); var alphaBitmap = _loadTexture(alphaTexMap); // Retreive dimensions int width = 0; int height = 0; var haveSameDimensions = TextureUtilities.GetMinimalBitmapDimensions(out width, out height, baseColorBitmap, alphaBitmap); if (!haveSameDimensions) { RaiseError("Base color and transparency color maps should have same dimensions", 3); } var getAlphaFromRGB = alphaTexture != null && ((alphaTexture.AlphaSource == 2) || (alphaTexture.AlphaSource == 3)); // 'RGB intensity' or 'None (Opaque)' // Create baseColor+alpha map var _baseColor = Color.FromArgb( (int)(baseColor[0] * 255), (int)(baseColor[1] * 255), (int)(baseColor[2] * 255)); var _alpha = (int)(alpha * 255); Bitmap baseColorAlphaBitmap = new Bitmap(width, height); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { var baseColorAtPixel = baseColorBitmap != null?baseColorBitmap.GetPixel(x, y) : _baseColor; Color baseColorAlpha; if (alphaBitmap != null) { // Retreive alpha from alpha texture Color alphaColor = alphaBitmap.GetPixel(x, y); int alphaAtPixel = getAlphaFromRGB ? alphaColor.R : alphaColor.A; if (isOpacity == false) { // Convert transparency to opacity alphaAtPixel = 255 - alphaAtPixel; } baseColorAlpha = Color.FromArgb(alphaAtPixel, baseColorAtPixel); } else if (baseColorTexture != null && baseColorTexture.AlphaSource == 0) // Alpha source is 'Image Alpha' { // Use all channels from base color baseColorAlpha = baseColorAtPixel; } else { // Use RGB channels from base color and default alpha baseColorAlpha = Color.FromArgb(_alpha, baseColorAtPixel.R, baseColorAtPixel.G, baseColorAtPixel.B); } baseColorAlphaBitmap.SetPixel(x, y, baseColorAlpha); } } // Write bitmap if (isBabylonExported) { RaiseMessage($"Texture | write image '{babylonTexture.name}'", 3); TextureUtilities.SaveBitmap(baseColorAlphaBitmap, babylonScene.OutputPath, babylonTexture.name, imageFormat, exportParameters.txtQuality, this); } else { // Store created bitmap for further use in gltf export babylonTexture.bitmap = baseColorAlphaBitmap; } } return(babylonTexture); } }
private BabylonTexture ExportClearCoatTexture(ITexmap intensityTexMap, ITexmap roughnessTexMap, float coatWeight, float coatRoughness, BabylonScene babylonScene, string materialName, bool invertRoughness) { // --- Babylon texture --- var intensityTexture = _getBitmapTex(intensityTexMap); var roughnessTexture = _getBitmapTex(roughnessTexMap); var texture = intensityTexture != null ? intensityTexture : roughnessTexture; if (texture == null) { return(null); } // Use one as a reference for UVs parameters RaiseMessage("Export Clear Coat weight+roughness texture", 2); string nameText = Path.GetFileNameWithoutExtension(texture.Map.FullFilePath); var textureID = texture.GetGuid().ToString(); if (textureMap.ContainsKey(textureID)) { return(textureMap[textureID]); } else { var babylonTexture = new BabylonTexture(textureID) { name = nameText // TODO - unsafe name, may conflict with another texture name }; // Level babylonTexture.level = 1.0f; // UVs var uvGen = _exportUV(texture.UVGen, babylonTexture); // Is cube _exportIsCube(texture.Map.FullFilePath, babylonTexture, false); // --- Merge maps --- var hasIntensity = isTextureOk(intensityTexture); var hasRoughness = isTextureOk(roughnessTexture); if (!hasIntensity && !hasRoughness) { return(null); } // Set image format ImageFormat imageFormat = ImageFormat.Jpeg; babylonTexture.name += ".jpg"; if (exportParameters.writeTextures) { // Load bitmaps var intensityBitmap = _loadTexture(intensityTexture); var roughnessBitmap = _loadTexture(roughnessTexture); // Retreive dimensions int width = 0; int height = 0; var haveSameDimensions = TextureUtilities.GetMinimalBitmapDimensions(out width, out height, intensityBitmap, roughnessBitmap); if (!haveSameDimensions) { RaiseError("Base color and transparency color maps should have same dimensions", 3); } // Create map var _intensity = (int)(coatWeight * 255); var _roughness = (int)(coatRoughness * 255); Bitmap intensityRoughnessBitmap = new Bitmap(width, height); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { var intensityAtPixel = (intensityBitmap == null) ? _intensity : intensityBitmap.GetPixel(x, y).R; Color intensityRoughness; if (roughnessBitmap == null) { intensityRoughness = Color.FromArgb(intensityAtPixel, _roughness, 0); } else { var roughnessAtPixel = (roughnessBitmap == null) ? _roughness : invertRoughness ? 255 - roughnessBitmap.GetPixel(x, y).G : roughnessBitmap.GetPixel(x, y).G; intensityRoughness = Color.FromArgb(intensityAtPixel, roughnessAtPixel, 0); } intensityRoughnessBitmap.SetPixel(x, y, intensityRoughness); } } // Write bitmap if (isBabylonExported) { RaiseMessage($"Texture | write image '{babylonTexture.name}'", 3); TextureUtilities.SaveBitmap(intensityRoughnessBitmap, babylonScene.OutputPath, babylonTexture.name, imageFormat, exportParameters.txtQuality, this); } else { // Store created bitmap for further use in gltf export babylonTexture.bitmap = intensityRoughnessBitmap; } } return(babylonTexture); } }
static IEnumerable <tk2DSpriteData> ReadSpritesFromData(SpriteCollectionData data) { var mainTexture = data.MainTexture as Texture2D; using (var progress = new ProgressBar(data.Definitions.GetLength(0) - 1, "Unpacking", "Unpacking TK2DSpriteDefinition", true)) { using (var readContext = new ReadableTextureContext(mainTexture)) { for (int i = 0; i < data.Definitions.GetLength(0); i++) { progress.GoToNextStep(); var sprite = data.Definitions[i]; Vector2 uvOffset = new Vector2(0.001f, 0.001f); Vector2 PostProcessedBL = Vector2.zero; Vector2 PostProcessedTR = Vector2.zero; bool rotated = false; if (sprite.UVs[0].x == sprite.UVs[1].x && sprite.UVs[2].x == sprite.UVs[3].x) { rotated = true; } if (rotated) { PostProcessedBL = sprite.UVs[1]; PostProcessedTR = sprite.UVs[2]; } else { PostProcessedBL = sprite.UVs[0]; PostProcessedTR = sprite.UVs[3]; } int PreBLx = Mathf.RoundToInt((PostProcessedBL.x * mainTexture.width) - uvOffset.x); int PreTRy = Mathf.RoundToInt(((PostProcessedBL.y) * mainTexture.height) - uvOffset.y); int PreTRx = Mathf.RoundToInt((PostProcessedTR.x * mainTexture.width) + uvOffset.x); int PreBLy = Mathf.RoundToInt(((PostProcessedTR.y) * mainTexture.height) + uvOffset.y); int PreWidth = Mathf.Abs(PreBLx - PreTRx); int PreHeight = Mathf.Abs(PreBLy - PreTRy); Orientation orientation = Orientation.Up; if (PostProcessedBL.x < PostProcessedTR.x && PostProcessedBL.y < PostProcessedTR.y) { orientation = Orientation.Up; } else if (PostProcessedBL.x < PostProcessedTR.x && PostProcessedBL.y > PostProcessedTR.y) { orientation = Orientation.Right; } else if (PostProcessedBL.x > PostProcessedTR.x && PostProcessedBL.y > PostProcessedTR.y) { orientation = Orientation.Down; } else if (PostProcessedBL.x > PostProcessedTR.x && PostProcessedBL.y < PostProcessedTR.y) { orientation = Orientation.Left; } Vector2Int Min = new Vector2Int(Mathf.RoundToInt(Mathf.Min(PreBLx, PreTRx)), Mathf.RoundToInt(Mathf.Min(PreBLy, PreTRy))); Vector2Int Max = new Vector2Int(Mathf.RoundToInt(Mathf.Max(PreBLx, PreTRx)), Mathf.RoundToInt(Mathf.Max(PreBLy, PreTRy))); Vector2Int SpriteDimensions = new Vector2Int(Mathf.Abs(Max.x - Min.x) + 1, Mathf.Abs(Max.y - Min.y) + 1); Texture2D texture = new Texture2D(SpriteDimensions.x, SpriteDimensions.y); if (Min.x < 0 || Min.y < 0 || Min.x + SpriteDimensions.x - 1 >= mainTexture.width || Min.y + SpriteDimensions.y - 1 >= mainTexture.height) { continue; } var test = mainTexture.GetPixels(Min.x, Min.y, SpriteDimensions.x, SpriteDimensions.y); texture.SetPixels(test); switch (orientation) { case Orientation.Up: texture.Rotate(RotationType.None); break; case Orientation.Right: texture.Rotate(RotationType.Right); break; case Orientation.Down: texture.Rotate(RotationType.HalfFullRotation); break; case Orientation.Left: texture.Rotate(RotationType.Left); break; default: break; } if (sprite.Flipped) { TextureUtilities.FlipHorizontally(texture); } texture.name = sprite.Name; var worldSize = GetWorldSize(sprite.Positions); //TODO yield return(new tk2DSpriteData() { Name = sprite.Name, Pivot = GetPivot(sprite.Positions), Texture = texture, UVDimensions = new Vector2Int(PreWidth, PreHeight), PixelsPerUnit = texture.width / worldSize.x, SpriteCoords = new Rect(Min.x, Min.y, Max.x - Min.x + 1, Max.y - Min.y + 1) }); } } } }
private void LoadTexture(Color col) { _coverage = TextureUtilities.CreateTextureFromColor(32, 32, col); }
public void Export(ExportParameters exportParameters) { this.exportParameters = exportParameters; IINode exportNode = null; if (exportParameters is MaxExportParameters) { MaxExportParameters maxExporterParameters = (exportParameters as MaxExportParameters); exportNode = maxExporterParameters.exportNode; if (maxExporterParameters.flattenScene) { FlattenHierarchy(exportNode); } if (maxExporterParameters.mergeInheritedContainers) { ExportClosedContainers(); } } this.scaleFactor = Tools.GetScaleFactorToMeters(); var scaleFactorFloat = 1.0f; // Check input text is valid float scaleFactor = exportParameters.scaleFactor; long quality = exportParameters.txtQuality; try { if (quality < 0 || quality > 100) { throw new Exception(); } } catch { RaiseError("Quality is not a valid number. It should be an integer between 0 and 100."); RaiseError("This parameter sets the quality of jpg compression."); return; } var gameConversionManger = Loader.Global.ConversionManager; gameConversionManger.CoordSystem = Autodesk.Max.IGameConversionManager.CoordSystem.D3d; var gameScene = Loader.Global.IGameInterface; if (exportNode == null || exportNode.IsRootNode) { gameScene.InitialiseIGame(false); } else { gameScene.InitialiseIGame(exportNode, true); } gameScene.SetStaticFrame(0); MaxSceneFileName = gameScene.SceneFileName; IsCancelled = false; string fileExportString = exportNode != null ? $"{exportNode.NodeName} | {exportParameters.outputPath}" : exportParameters.outputPath; RaiseMessage($"Exportation started: {fileExportString}", Color.Blue); ReportProgressChanged(0); string tempOutputDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); string outputDirectory = Path.GetDirectoryName(exportParameters.outputPath); string folderOuputDirectory = exportParameters.textureFolder; string outputFileName = Path.GetFileName(exportParameters.outputPath); // Check directory exists if (!Directory.Exists(outputDirectory)) { RaiseError("Exportation stopped: Output folder does not exist"); ReportProgressChanged(100); return; } Directory.CreateDirectory(tempOutputDirectory); var outputBabylonDirectory = tempOutputDirectory; // Force output file extension to be babylon outputFileName = Path.ChangeExtension(outputFileName, "babylon"); var babylonScene = new BabylonScene(outputBabylonDirectory); var rawScene = Loader.Core.RootNode; var watch = new Stopwatch(); watch.Start(); string outputFormat = exportParameters.outputFormat; isBabylonExported = outputFormat == "babylon" || outputFormat == "binary babylon"; isGltfExported = outputFormat == "gltf" || outputFormat == "glb"; // Get scene parameters optimizeAnimations = !Loader.Core.RootNode.GetBoolProperty("babylonjs_donotoptimizeanimations"); exportNonAnimated = Loader.Core.RootNode.GetBoolProperty("babylonjs_animgroup_exportnonanimated"); // Save scene if (exportParameters.autoSaveSceneFile) { RaiseMessage("Saving 3ds max file"); var forceSave = Loader.Core.FileSave; callerForm?.BringToFront(); } // Producer babylonScene.producer = new BabylonProducer { name = "3dsmax", #if MAX2019 version = "2019", #elif MAX2018 version = "2018", #elif MAX2017 version = "2017", #else version = Loader.Core.ProductVersion.ToString(), #endif exporter_version = exporterVersion, file = outputFileName }; // Global babylonScene.autoClear = true; babylonScene.clearColor = Loader.Core.GetBackGround(0, Tools.Forever).ToArray(); babylonScene.ambientColor = Loader.Core.GetAmbient(0, Tools.Forever).ToArray(); babylonScene.TimelineStartFrame = Loader.Core.AnimRange.Start / Loader.Global.TicksPerFrame; babylonScene.TimelineEndFrame = Loader.Core.AnimRange.End / Loader.Global.TicksPerFrame; babylonScene.TimelineFramesPerSecond = MaxSceneTicksPerSecond / Loader.Global.TicksPerFrame; babylonScene.gravity = rawScene.GetVector3Property("babylonjs_gravity"); ExportQuaternionsInsteadOfEulers = rawScene.GetBoolProperty("babylonjs_exportquaternions", 1); if (string.IsNullOrEmpty(exportParameters.pbrEnvironment) && Loader.Core.UseEnvironmentMap && Loader.Core.EnvironmentMap != null) { // Environment texture var environmentMap = Loader.Core.EnvironmentMap; // Copy image file to output if necessary var babylonTexture = ExportEnvironmnentTexture(environmentMap, babylonScene); if (babylonTexture != null) { babylonScene.environmentTexture = babylonTexture.name; // Skybox babylonScene.createDefaultSkybox = rawScene.GetBoolProperty("babylonjs_createDefaultSkybox"); babylonScene.skyboxBlurLevel = rawScene.GetFloatProperty("babylonjs_skyboxBlurLevel"); } } else if (!string.IsNullOrEmpty(exportParameters.pbrEnvironment)) { babylonScene.createDefaultSkybox = rawScene.GetBoolProperty("babylonjs_createDefaultSkybox"); babylonScene.skyboxBlurLevel = rawScene.GetFloatProperty("babylonjs_skyboxBlurLevel"); } // Instantiate custom material exporters materialExporters = new Dictionary <ClassIDWrapper, IMaxMaterialExporter>(); foreach (Type type in Tools.GetAllLoadableTypes()) { if (type.IsAbstract || type.IsInterface || !typeof(IMaxMaterialExporter).IsAssignableFrom(type)) { continue; } IMaxMaterialExporter exporter = Activator.CreateInstance(type) as IMaxMaterialExporter; if (exporter == null) { RaiseWarning("Creating exporter instance failed: " + type.Name, 1); } materialExporters.Add(exporter.MaterialClassID, exporter); } // Sounds var soundName = rawScene.GetStringProperty("babylonjs_sound_filename", ""); if (!string.IsNullOrEmpty(soundName)) { var filename = Path.GetFileName(soundName); var globalSound = new BabylonSound { autoplay = rawScene.GetBoolProperty("babylonjs_sound_autoplay", 1), loop = rawScene.GetBoolProperty("babylonjs_sound_loop", 1), name = filename }; babylonScene.SoundsList.Add(globalSound); if (isBabylonExported) { try { File.Copy(soundName, Path.Combine(babylonScene.OutputPath, filename), true); } catch { } } } // Root nodes RaiseMessage("Exporting nodes"); HashSet <IIGameNode> maxRootNodes = getRootNodes(gameScene); var progressionStep = 80.0f / maxRootNodes.Count; var progression = 10.0f; ReportProgressChanged((int)progression); referencedMaterials.Clear(); Tools.guids.Clear(); // Reseting is optionnal. It makes each morph target manager export starts from id = 0. BabylonMorphTargetManager.Reset(); foreach (var maxRootNode in maxRootNodes) { BabylonNode node = exportNodeRec(maxRootNode, babylonScene, gameScene); // if we're exporting from a specific node, reset the pivot to {0,0,0} if (node != null && exportNode != null && !exportNode.IsRootNode) { SetNodePosition(ref node, ref babylonScene, new float[] { 0, 0, 0 }); } progression += progressionStep; ReportProgressChanged((int)progression); CheckCancelled(); } ; RaiseMessage(string.Format("Total meshes: {0}", babylonScene.MeshesList.Count), Color.Gray, 1); // In 3DS Max the default camera look down (in the -z direction for the 3DS Max reference (+y for babylon)) // In Babylon the default camera look to the horizon (in the +z direction for the babylon reference) // In glTF the default camera look to the horizon (in the +Z direction for glTF reference) RaiseMessage("Update camera rotation and position", 1); for (int index = 0; index < babylonScene.CamerasList.Count; index++) { BabylonCamera camera = babylonScene.CamerasList[index]; FixCamera(ref camera, ref babylonScene); } // Light for glTF if (isGltfExported) { RaiseMessage("Update light rotation for glTF export", 1); for (int index = 0; index < babylonScene.LightsList.Count; index++) { BabylonNode light = babylonScene.LightsList[index]; FixNodeRotation(ref light, ref babylonScene, -Math.PI / 2); } } // Main camera BabylonCamera babylonMainCamera = null; ICameraObject maxMainCameraObject = null; if (babylonMainCamera == null && babylonScene.CamerasList.Count > 0) { // Set first camera as main one babylonMainCamera = babylonScene.CamerasList[0]; babylonScene.activeCameraID = babylonMainCamera.id; RaiseMessage("Active camera set to " + babylonMainCamera.name, Color.Green, 1, true); // Retreive camera node with same GUID var maxCameraNodesAsTab = gameScene.GetIGameNodeByType(Autodesk.Max.IGameObject.ObjectTypes.Camera); var maxCameraNodes = TabToList(maxCameraNodesAsTab); var maxMainCameraNode = maxCameraNodes.Find(_camera => _camera.MaxNode.GetGuid().ToString() == babylonMainCamera.id); maxMainCameraObject = (maxMainCameraNode.MaxNode.ObjectRef as ICameraObject); } if (babylonMainCamera == null) { RaiseWarning("No camera defined", 1); } else { RaiseMessage(string.Format("Total cameras: {0}", babylonScene.CamerasList.Count), Color.Gray, 1); } // Default light bool addDefaultLight = rawScene.GetBoolProperty("babylonjs_addDefaultLight", 1); if (!exportParameters.pbrNoLight && addDefaultLight && babylonScene.LightsList.Count == 0) { RaiseWarning("No light defined", 1); RaiseWarning("A default hemispheric light was added for your convenience", 1); ExportDefaultLight(babylonScene); } else { RaiseMessage(string.Format("Total lights: {0}", babylonScene.LightsList.Count), Color.Gray, 1); } if (scaleFactorFloat != 1.0f) { RaiseMessage("A root node is added for scaling", 1); // Create root node for scaling BabylonMesh rootNode = new BabylonMesh { name = "root", id = Guid.NewGuid().ToString() }; rootNode.isDummy = true; float rootNodeScale = scaleFactorFloat; rootNode.scaling = new float[3] { rootNodeScale, rootNodeScale, rootNodeScale }; if (ExportQuaternionsInsteadOfEulers) { rootNode.rotationQuaternion = new float[] { 0, 0, 0, 1 }; } else { rootNode.rotation = new float[] { 0, 0, 0 }; } // Update all top nodes var babylonNodes = new List <BabylonNode>(); babylonNodes.AddRange(babylonScene.MeshesList); babylonNodes.AddRange(babylonScene.CamerasList); babylonNodes.AddRange(babylonScene.LightsList); foreach (BabylonNode babylonNode in babylonNodes) { if (babylonNode.parentId == null) { babylonNode.parentId = rootNode.id; } } // Store root node babylonScene.MeshesList.Add(rootNode); } // Materials if (exportParameters.exportMaterials) { RaiseMessage("Exporting materials"); var matsToExport = referencedMaterials.ToArray(); // Snapshot because multimaterials can export new materials foreach (var mat in matsToExport) { ExportMaterial(mat, babylonScene); CheckCancelled(); } RaiseMessage(string.Format("Total: {0}", babylonScene.MaterialsList.Count + babylonScene.MultiMaterialsList.Count), Color.Gray, 1); } else { RaiseMessage("Skipping material export."); } // Fog for (var index = 0; index < Loader.Core.NumAtmospheric; index++) { var atmospheric = Loader.Core.GetAtmospheric(index); if (atmospheric.Active(0) && atmospheric.ClassName == "Fog") { var fog = atmospheric as IStdFog; RaiseMessage("Exporting fog"); if (fog != null) { babylonScene.fogColor = fog.GetColor(0).ToArray(); babylonScene.fogMode = 3; } if (babylonMainCamera != null) { babylonScene.fogStart = maxMainCameraObject.GetEnvRange(0, 0, Tools.Forever); babylonScene.fogEnd = maxMainCameraObject.GetEnvRange(0, 1, Tools.Forever); } } } // Skeletons if (skins.Count > 0) { RaiseMessage("Exporting skeletons"); foreach (var skin in skins) { ExportSkin(skin, babylonScene); } } // ---------------------------- // ----- Animation groups ----- // ---------------------------- RaiseMessage("Export animation groups"); // add animation groups to the scene babylonScene.animationGroups = ExportAnimationGroups(babylonScene); if (isBabylonExported) { // if we are exporting to .Babylon then remove then remove animations from nodes if there are animation groups. if (babylonScene.animationGroups.Count > 0) { foreach (BabylonNode node in babylonScene.MeshesList) { node.animations = null; } foreach (BabylonNode node in babylonScene.LightsList) { node.animations = null; } foreach (BabylonNode node in babylonScene.CamerasList) { node.animations = null; } foreach (BabylonSkeleton skel in babylonScene.SkeletonsList) { foreach (BabylonBone bone in skel.bones) { bone.animation = null; } } } // setup a default skybox for the scene for .Babylon export. var sourcePath = exportParameters.pbrEnvironment; if (!string.IsNullOrEmpty(sourcePath)) { var fileName = Path.GetFileName(sourcePath); // Allow only dds file format if (!fileName.EndsWith(".dds")) { RaiseWarning("Failed to export defauenvironment texture: only .dds format is supported."); } else { RaiseMessage($"texture id = Max_Babylon_Default_Environment"); babylonScene.environmentTexture = fileName; if (exportParameters.writeTextures) { try { var destPath = Path.Combine(babylonScene.OutputPath, fileName); if (File.Exists(sourcePath) && sourcePath != destPath) { File.Copy(sourcePath, destPath, true); } } catch { // silently fails RaiseMessage($"Fail to export the default env texture", 3); } } } } } // Output babylonScene.Prepare(false, false); if (isBabylonExported) { RaiseMessage("Saving to output file"); var outputFile = Path.Combine(outputBabylonDirectory, outputFileName); var jsonSerializer = JsonSerializer.Create(new JsonSerializerSettings()); var sb = new StringBuilder(); var sw = new StringWriter(sb, CultureInfo.InvariantCulture); using (var jsonWriter = new JsonTextWriterOptimized(sw)) { jsonWriter.Formatting = Formatting.None; jsonSerializer.Serialize(jsonWriter, babylonScene); } File.WriteAllText(outputFile, sb.ToString()); if (exportParameters.generateManifest) { File.WriteAllText(outputFile + ".manifest", "{\r\n\"version\" : 1,\r\n\"enableSceneOffline\" : true,\r\n\"enableTexturesOffline\" : true\r\n}"); } // Binary if (outputFormat == "binary babylon") { RaiseMessage("Generating binary files"); BabylonFileConverter.BinaryConverter.Convert(outputFile, outputBabylonDirectory + "\\Binary", message => RaiseMessage(message, 1), error => RaiseError(error, 1)); } } ReportProgressChanged(100); // Export glTF if (isGltfExported) { bool generateBinary = outputFormat == "glb"; GLTFExporter gltfExporter = new GLTFExporter(); exportParameters.customGLTFMaterialExporter = new MaxGLTFMaterialExporter(exportParameters, gltfExporter, this); gltfExporter.ExportGltf(this.exportParameters, babylonScene, tempOutputDirectory, outputFileName, generateBinary, this); } // Move files to output directory var filePaths = Directory.GetFiles(tempOutputDirectory); if (outputFormat == "binary babylon") { var tempBinaryOutputDirectory = Path.Combine(tempOutputDirectory, "Binary"); var binaryFilePaths = Directory.GetFiles(tempBinaryOutputDirectory); foreach (var filePath in binaryFilePaths) { if (filePath.EndsWith(".binary.babylon")) { var file = Path.GetFileName(filePath); var tempFilePath = Path.Combine(tempBinaryOutputDirectory, file); var outputFile = Path.Combine(outputDirectory, file); IUTF8Str maxNotification = GlobalInterface.Instance.UTF8Str.Create(outputFile); Loader.Global.BroadcastNotification(SystemNotificationCode.PreExport, maxNotification); moveFileToOutputDirectory(tempFilePath, outputFile, exportParameters); Loader.Global.BroadcastNotification(SystemNotificationCode.PostExport, maxNotification); } else if (filePath.EndsWith(".babylonbinarymeshdata")) { var file = Path.GetFileName(filePath); var tempFilePath = Path.Combine(tempBinaryOutputDirectory, file); var outputFile = Path.Combine(outputDirectory, file); IUTF8Str maxNotification = GlobalInterface.Instance.UTF8Str.Create(outputFile); Loader.Global.BroadcastNotification(SystemNotificationCode.PreExport, maxNotification); moveFileToOutputDirectory(tempFilePath, outputFile, exportParameters); Loader.Global.BroadcastNotification(SystemNotificationCode.PostExport, maxNotification); } } } if (outputFormat == "glb") { foreach (var file_path in filePaths) { if (Path.GetExtension(file_path) == ".glb") { var file = Path.GetFileName(file_path); var tempFilePath = Path.Combine(tempOutputDirectory, file); var outputFile = Path.Combine(outputDirectory, file); IUTF8Str maxNotification = GlobalInterface.Instance.UTF8Str.Create(outputFile); Loader.Global.BroadcastNotification(SystemNotificationCode.PreExport, maxNotification); moveFileToOutputDirectory(tempFilePath, outputFile, exportParameters); Loader.Global.BroadcastNotification(SystemNotificationCode.PostExport, maxNotification); break; } } } else { foreach (var filePath in filePaths) { var file = Path.GetFileName(filePath); string ext = Path.GetExtension(file); var tempFilePath = Path.Combine(tempOutputDirectory, file); var outputPath = Path.Combine(outputDirectory, file); if (!string.IsNullOrWhiteSpace(exportParameters.textureFolder) && TextureUtilities.ExtensionIsValidGLTFTexture(ext)) { outputPath = Path.Combine(exportParameters.textureFolder, file); } IUTF8Str maxNotification = GlobalInterface.Instance.UTF8Str.Create(outputPath); Loader.Global.BroadcastNotification(SystemNotificationCode.PreExport, maxNotification); moveFileToOutputDirectory(tempFilePath, outputPath, exportParameters); Loader.Global.BroadcastNotification(SystemNotificationCode.PostExport, maxNotification); } } Directory.Delete(tempOutputDirectory, true); watch.Stop(); RaiseMessage(string.Format("Exportation done in {0:0.00}s: {1}", watch.ElapsedMilliseconds / 1000.0, fileExportString), Color.Blue); IUTF8Str max_notification = Autodesk.Max.GlobalInterface.Instance.UTF8Str.Create("BabylonExportComplete"); Loader.Global.BroadcastNotification(SystemNotificationCode.PostExport, max_notification); }
private GLTFTextureInfo ExportTexture(BabylonTexture babylonTexture, GLTF gltf, string name) { if (babylonTexture == null) { return(null); } if (name == null) { name = babylonTexture.name; } logger.RaiseMessage("GLTFExporter.Texture | Export texture named: " + name, 2); if (glTFTextureInfoMap.ContainsKey(babylonTexture.Id)) { return(glTFTextureInfoMap[babylonTexture.Id]); } else { var sourcePath = babylonTexture.originalPath; if (babylonTexture.bitmap != null) { sourcePath = Path.Combine(gltf.OutputFolder, name); } if (sourcePath == null || sourcePath == "") { logger.RaiseWarning("Texture path is missing.", 3); return(null); } var validImageFormat = TextureUtilities.GetValidImageFormat(Path.GetExtension(sourcePath)); if (validImageFormat == null) { // Image format is not supported by the exporter logger.RaiseWarning(string.Format("Format of texture {0} is not supported by the exporter. Consider using a standard image format like jpg or png.", Path.GetFileName(sourcePath)), 3); return(null); } var destPath = Path.Combine(gltf.OutputFolder, name); destPath = Path.ChangeExtension(destPath, validImageFormat); name = Path.ChangeExtension(name, validImageFormat); // -------------------------- // -------- Sampler --------- // -------------------------- logger.RaiseMessage("GLTFExporter.Texture | create sampler", 3); GLTFSampler gltfSampler = new GLTFSampler(); gltfSampler.index = gltf.SamplersList.Count; // --- Retrieve info from babylon texture --- // Mag and min filters GLTFSampler.TextureMagFilter?magFilter; GLTFSampler.TextureMinFilter?minFilter; getSamplingParameters(babylonTexture.samplingMode, out magFilter, out minFilter); gltfSampler.magFilter = magFilter; gltfSampler.minFilter = minFilter; // WrapS and wrapT gltfSampler.wrapS = getWrapMode(babylonTexture.wrapU); gltfSampler.wrapT = getWrapMode(babylonTexture.wrapV); var matchingSampler = gltf.SamplersList.FirstOrDefault(sampler => sampler.wrapS == gltfSampler.wrapS && sampler.wrapT == gltfSampler.wrapT && sampler.magFilter == gltfSampler.magFilter && sampler.minFilter == gltfSampler.minFilter); if (matchingSampler != null) { gltfSampler = matchingSampler; } else { gltf.SamplersList.Add(gltfSampler); } // -------------------------- // --------- Image ---------- // -------------------------- logger.RaiseMessage("GLTFExporter.Texture | create image", 3); GLTFImage gltfImage = null; if (glTFImageMap.ContainsKey(name)) { gltfImage = glTFImageMap[name]; } else { string textureUri = name; if (!string.IsNullOrWhiteSpace(exportParameters.textureFolder)) { textureUri = PathUtilities.GetRelativePath(exportParameters.outputPath, exportParameters.textureFolder); textureUri = Path.Combine(textureUri, name); } gltfImage = new GLTFImage { uri = textureUri }; gltfImage.index = gltf.ImagesList.Count; gltf.ImagesList.Add(gltfImage); switch (validImageFormat) { case "jpg": gltfImage.FileExtension = "jpeg"; break; case "png": gltfImage.FileExtension = "png"; break; } if (exportParameters.outputFormat == "glb") { var imageBufferView = WriteImageToGltfBuffer(gltf, gltfImage, sourcePath, babylonTexture.bitmap); gltfImage.uri = null; gltfImage.bufferView = imageBufferView.index; gltfImage.mimeType = "image/" + gltfImage.FileExtension; } else { if (exportParameters.writeTextures) { if (babylonTexture.bitmap != null) { // We may have modified this texture image, copy the buffer contents to disk var extension = Path.GetExtension(name).ToLower(); var imageFormat = extension == ".jpg" ? System.Drawing.Imaging.ImageFormat.Jpeg : System.Drawing.Imaging.ImageFormat.Png; logger.RaiseMessage($"GLTFExporter.Texture | write image '{name}' to '{destPath}'", 3); TextureUtilities.SaveBitmap(babylonTexture.bitmap, destPath, imageFormat, exportParameters.txtQuality, logger); } else { // Copy texture from source to output TextureUtilities.CopyTexture(sourcePath, destPath, exportParameters.txtQuality, logger); } } } glTFImageMap.Add(name, gltfImage); } // -------------------------- // -------- Texture --------- // -------------------------- logger.RaiseMessage("GLTFExporter.Texture | create texture", 3); var gltfTexture = new GLTFTexture { name = name, sampler = gltfSampler.index, source = gltfImage.index }; gltfTexture.index = gltf.TexturesList.Count; if (!CheckIfImageIsRegistered(name)) { gltf.TexturesList.Add(gltfTexture); } else { gltfTexture = gltf.TexturesList[GetRegisteredTexture(gltfTexture.name).index]; } // -------------------------- // ------ TextureInfo ------- // -------------------------- var gltfTextureInfo = new GLTFTextureInfo { index = gltfTexture.index, texCoord = babylonTexture.coordinatesIndex }; if (!(babylonTexture.uOffset == 0) || !(babylonTexture.vOffset == 0) || !(babylonTexture.uScale == 1) || !(babylonTexture.vScale == 1) || !(babylonTexture.wAng == 0)) { // Add texture extension if enabled in the export settings if (exportParameters.enableKHRTextureTransform) { AddTextureTransformExtension(ref gltf, ref gltfTextureInfo, babylonTexture); } else { logger.RaiseWarning("GLTFExporter.Texture | KHR_texture_transform is not enabled, so the texture may look incorrect at runtime!", 3); logger.RaiseWarning("GLTFExporter.Texture | KHR_texture_transform is not enabled, so the texture may look incorrect at runtime!", 3); } } var textureID = name + TextureTransformID(gltfTextureInfo); // Check for texture optimization. This is done here after the texture transform has been potentially applied to the texture extension if (CheckIfImageIsRegistered(textureID)) { var textureComponent = GetRegisteredTexture(textureID); return(textureComponent); } // Add the texture in the dictionary RegisterTexture(gltfTextureInfo, textureID); glTFTextureInfoMap[babylonTexture.Id] = gltfTextureInfo; return(gltfTextureInfo); } }
private void GeneratePackedTexture() { var redChannelInput = _channelRedTextureObjectField.value; var greenChannelInput = _channelGreenTextureObjectField.value; var blueChannelInput = _channelBlueTextureObjectField.value; var alphaChannelInput = _channelAlphaTextureObjectField.value; var width = _widthIntField.value; var height = _heightIntField.value; var redChannelTexture = (Texture2D)redChannelInput; var greenChannelTexture = (Texture2D)greenChannelInput; var blueChannelTexture = (Texture2D)blueChannelInput; var alphaChannelTexture = (Texture2D)alphaChannelInput; // Create the composite texture Texture2D compositeTexture; if (_scaleToSpecificValueToggle.value) { compositeTexture = TexturePacker.GetCompositeTextureRgb( _nameIdentifierTextField.text, redChannelTexture, greenChannelTexture, blueChannelTexture, alphaChannelTexture, (int)_textureSizeVectorField.value.x, (int)_textureSizeVectorField.value.y); } else { compositeTexture = TexturePacker.GetCompositeTextureRgb( _nameIdentifierTextField.text, redChannelTexture, greenChannelTexture, blueChannelTexture, alphaChannelTexture); } // If inputs are valid, add their names to a list var validInputsNameList = new List <string>(); if (redChannelTexture) { validInputsNameList.Add(redChannelTexture.name); } if (greenChannelTexture) { validInputsNameList.Add(greenChannelTexture.name); } if (blueChannelTexture) { validInputsNameList.Add(blueChannelTexture.name); } if (alphaChannelTexture) { validInputsNameList.Add(alphaChannelTexture.name); } // Find the common substring in the names and add the identifier var compositeTextureName = StringUtilities.GetCommonPrefix(validInputsNameList) + _nameIdentifierTextField.text; // Find the first valid input and create the path based on that var listOfTextureInputs = new List <Texture2D>() { redChannelTexture, redChannelTexture, redChannelTexture, alphaChannelTexture }; var relativeCompositeTexturePath = Path.Combine( Path.GetDirectoryName(AssetDatabase.GetAssetPath( GetFirstValidTextureInput(listOfTextureInputs))), compositeTextureName); // Save the composite texture var hasSaved = false; if (_tgaToggle.value) { var newRelativeCompositeTexturePath = relativeCompositeTexturePath + _tgaToggle.label; var absoluteCompositeTexturePath = Path.Combine( Directory.GetParent(Application.dataPath).FullName, newRelativeCompositeTexturePath); TextureUtilities.SaveTextureToPath( compositeTexture, absoluteCompositeTexturePath, TextureUtilities.TextureUtilitiesFormats.Tga); hasSaved = true; } if (_pngToggle.value) { var newRelativeCompositeTexturePath = relativeCompositeTexturePath + _pngToggle.label; var absoluteCompositeTexturePath = Path.Combine( Directory.GetParent(Application.dataPath).FullName, newRelativeCompositeTexturePath); TextureUtilities.SaveTextureToPath( compositeTexture, absoluteCompositeTexturePath, TextureUtilities.TextureUtilitiesFormats.Png); hasSaved = true; } // Update the previews for the provided images _previewImageRedChannelVisualElement.style.backgroundImage = redChannelTexture; _previewImageGreenChannelVisualElement.style.backgroundImage = greenChannelTexture; _previewImageBlueChannelVisualElement.style.backgroundImage = blueChannelTexture; _previewImageAlphaChannelVisualElement.style.backgroundImage = alphaChannelTexture; // If a new texture has been saved to disk, display it and select in the Project Window if (hasSaved) { AssetDatabase.Refresh(); compositeTexture.Apply(); _previewImageResultVisualElement.style.backgroundImage = compositeTexture; Selection.activeObject = AssetDatabase.LoadAssetAtPath(relativeCompositeTexturePath, typeof(Texture2D)); } }
/// <returns></returns> private BabylonTexture ExportBaseColorAlphaTexture(ITexmap baseColorTexMap, ITexmap alphaTexMap, float[] baseColor, float alpha, BabylonScene babylonScene, string materialName, bool isOpacity = false) { // --- Babylon texture --- var baseColorTexture = _getBitmapTex(baseColorTexMap); var alphaTexture = _getBitmapTex(alphaTexMap); string baseColorTextureMapExtension = null; // If we don't retrieve any textures from Max, return null. if (baseColorTexture == null && alphaTexture == null) { return(null); } // If we only have a base color texture, and we are using an opaque texture, export the base color image only. if (baseColorTexture != null && alphaTexture == null) { baseColorTextureMapExtension = Path.GetExtension(baseColorTexture.Map.FullFilePath).ToLower(); if (alpha == 1) { if (baseColorTexture.AlphaSource == MaxConstants.IMAGE_ALPHA_FILE && (baseColorTextureMapExtension == ".tif" || baseColorTextureMapExtension == ".tiff")) { RaiseWarning($"Diffuse texture named {baseColorTexture.Map.FullFilePath} is a .tif file and its Alpha Source is 'Image Alpha' by default.", 3); RaiseWarning($"If you don't want material to be in BLEND mode, set diffuse texture Alpha Source to 'None (Opaque)'", 3); } // Copy base color image var outTexture = ExportTexture(baseColorTexture, babylonScene); textureMap[outTexture.Id] = outTexture; return(outTexture); } } // Otherwise combine base color and alpha textures to a single output texture RaiseMessage("Export baseColor+Alpha texture", 2); var hasBaseColor = baseColorTexture != null && isTextureOk(baseColorTexMap); var hasAlpha = isTextureOk(alphaTexMap); var texture = hasBaseColor ? baseColorTexture : alphaTexture; ImageFormat imageFormat = null; if (hasBaseColor) { imageFormat = TextureUtilities.GetImageFormat(Path.GetExtension(baseColorTexture.Map.FullFilePath)); } if (hasAlpha || imageFormat == null) { baseColorTextureMapExtension = ".png"; // since we are adding an alpha channel, export as png. This will convert any other input base texture format to PNG. imageFormat = ImageFormat.Png; } // since we are creating a new texture, give it a unique ID based on the base color and alpha maps. var nameText = (hasBaseColor ? Path.GetFileNameWithoutExtension(baseColorTexture.Map.FullFilePath) + (hasAlpha ? "_" + Path.GetFileNameWithoutExtension(alphaTexture.Map.FullFilePath) : "") : TextureUtilities.ColorToStringName(baseColor)); var textureID = hasBaseColor ? texture.GetGuid().ToString() + (hasAlpha ? "_" + alphaTexture.GetGuid().ToString() : "") : string.Format("{0}_{1}", texture.GetGuid().ToString(), nameText); if (textureMap.ContainsKey(textureID)) { return(textureMap[textureID]); } BabylonTexture babylonTexture = null; babylonTexture = new BabylonTexture(textureID) { name = nameText }; // Alpha babylonTexture.hasAlpha = hasAlpha || (hasBaseColor && (baseColorTexture.AlphaSource == MaxConstants.IMAGE_ALPHA_FILE || baseColorTexture.AlphaSource == MaxConstants.IMAGE_ALPHA_RGB)) || alpha < 1.0f; babylonTexture.getAlphaFromRGB = false; if (!hasBaseColor && !hasAlpha) { return(null); } if ((!isTextureOk(alphaTexMap) && alpha == 1.0f && (isTextureOk(baseColorTexMap) && baseColorTexture.AlphaSource == MaxConstants.IMAGE_ALPHA_FILE)) && (baseColorTextureMapExtension == ".tif" || baseColorTextureMapExtension == ".tiff")) { RaiseWarning($"Diffuse texture named {baseColorTexture.Map.FullFilePath} is a .tif file and its Alpha Source is 'Image Alpha' by default.", 3); RaiseWarning($"If you don't want material to be in BLEND mode, set diffuse texture Alpha Source to 'None (Opaque)'", 3); } // Set image format if (hasAlpha) { babylonTexture.name += "_alpha_" + alphaTexture.Name; } if (imageFormat == ImageFormat.Jpeg) { babylonTexture.name += ".jpg"; } else { babylonTexture.name += "." + imageFormat.ToString(); } // Level babylonTexture.level = 1.0f; // UVs var uvGen = _exportUV(texture.UVGen, babylonTexture); // Is cube _exportIsCube(texture.Map.FullFilePath, babylonTexture, false); // --- Merge baseColor and alpha maps --- if (exportParameters.writeTextures && baseColorTexture != alphaTexture && alphaTexture != null) { // Load bitmaps var baseColorBitmap = _loadTexture(baseColorTexMap); var alphaBitmap = _loadTexture(alphaTexMap); // Retreive dimensions int width = 0; int height = 0; var haveSameDimensions = TextureUtilities.GetMinimalBitmapDimensions(out width, out height, baseColorBitmap, alphaBitmap); if (!haveSameDimensions) { RaiseError("Base color and transparency color maps should have same dimensions", 3); } var getAlphaFromRGB = alphaTexture != null && ((alphaTexture.AlphaSource == MaxConstants.IMAGE_ALPHA_RGB) || (alphaTexture.AlphaSource == MaxConstants.IMAGE_ALPHA_NONE)); // 'RGB intensity' or 'None (Opaque)' // Create baseColor+alpha map var _baseColor = Color.FromArgb( (int)(baseColor[0] * 255), (int)(baseColor[1] * 255), (int)(baseColor[2] * 255)); var _alpha = (int)(alpha * 255); Bitmap baseColorAlphaBitmap = new Bitmap(width, height); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { var baseColorAtPixel = baseColorBitmap != null?baseColorBitmap.GetPixel(x, y) : _baseColor; Color baseColorAlpha; if (alphaBitmap != null) { // Retreive alpha from alpha texture Color alphaColor = alphaBitmap.GetPixel(x, y); int alphaAtPixel = getAlphaFromRGB ? alphaColor.R : alphaColor.A; if (isOpacity == false) { // Convert transparency to opacity alphaAtPixel = 255 - alphaAtPixel; } baseColorAlpha = Color.FromArgb(alphaAtPixel, baseColorAtPixel); } else if (baseColorTexture != null && baseColorTexture.AlphaSource == MaxConstants.IMAGE_ALPHA_FILE) // Alpha source is 'Image Alpha' { // Use all channels from base color baseColorAlpha = baseColorAtPixel; } else { // Use RGB channels from base color and default alpha baseColorAlpha = Color.FromArgb(_alpha, baseColorAtPixel.R, baseColorAtPixel.G, baseColorAtPixel.B); } baseColorAlphaBitmap.SetPixel(x, y, baseColorAlpha); } } // Write bitmap if (isBabylonExported) { RaiseMessage($"Texture | write image '{babylonTexture.name}'", 3); TextureUtilities.SaveBitmap(baseColorAlphaBitmap, babylonScene.OutputPath, babylonTexture.name, imageFormat, exportParameters.txtQuality, this); } else { // Store created bitmap for further use in gltf export babylonTexture.bitmap = baseColorAlphaBitmap; } } return(babylonTexture); }
private string GetGltfValidImageFormat(string extension) { return(TextureUtilities.GetValidImageFormat(extension)); }
private void ExportMaterial(BabylonMaterial babylonMaterial, GLTF gltf) { var name = babylonMaterial.name; var id = babylonMaterial.id; logger.RaiseMessage("GLTFExporter.Material | Export material named: " + name, 1); GLTFMaterial gltfMaterial = null; string message = null; IGLTFMaterialExporter customMaterialExporter = exportParameters.customGLTFMaterialExporter; if (customMaterialExporter != null && customMaterialExporter.GetGltfMaterial(babylonMaterial, gltf, logger, out gltfMaterial)) { gltfMaterial.index = gltf.MaterialsList.Count; gltf.MaterialsList.Add(gltfMaterial); } else if (babylonMaterial.GetType() == typeof(BabylonStandardMaterial)) { var babylonStandardMaterial = babylonMaterial as BabylonStandardMaterial; // --- prints --- #region prints logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial data", 2); logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial.alpha=" + babylonMaterial.alpha, 3); logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial.alphaMode=" + babylonMaterial.alphaMode, 3); logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial.backFaceCulling=" + babylonMaterial.backFaceCulling, 3); logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial.wireframe=" + babylonMaterial.wireframe, 3); // Ambient for (int i = 0; i < babylonStandardMaterial.ambient.Length; i++) { logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.ambient[" + i + "]=" + babylonStandardMaterial.ambient[i], 3); } // Diffuse logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.diffuse.Length=" + babylonStandardMaterial.diffuse.Length, 3); for (int i = 0; i < babylonStandardMaterial.diffuse.Length; i++) { logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.diffuse[" + i + "]=" + babylonStandardMaterial.diffuse[i], 3); } if (babylonStandardMaterial.diffuseTexture == null) { logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.diffuseTexture=null", 3); } else { logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.diffuseTexture.name=" + babylonStandardMaterial.diffuseTexture.name, 3); } // Normal / bump if (babylonStandardMaterial.bumpTexture == null) { logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.bumpTexture=null", 3); } else { logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.bumpTexture.name=" + babylonStandardMaterial.bumpTexture.name, 3); } // Opacity if (babylonStandardMaterial.opacityTexture == null) { logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.opacityTexture=null", 3); } else { logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.opacityTexture.name=" + babylonStandardMaterial.opacityTexture.name, 3); } // Specular for (int i = 0; i < babylonStandardMaterial.specular.Length; i++) { logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.specular[" + i + "]=" + babylonStandardMaterial.specular[i], 3); } logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.specularPower=" + babylonStandardMaterial.specularPower, 3); if (babylonStandardMaterial.specularTexture == null) { logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.specularTexture=null", 3); } else { logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.specularTexture.name=" + babylonStandardMaterial.specularTexture.name, 3); } // Occlusion if (babylonStandardMaterial.ambientTexture == null) { logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.ambientTexture=null", 3); } else { logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.ambientTexture.name=" + babylonStandardMaterial.ambientTexture.name, 3); } // Emissive for (int i = 0; i < babylonStandardMaterial.emissive.Length; i++) { logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.emissive[" + i + "]=" + babylonStandardMaterial.emissive[i], 3); } if (babylonStandardMaterial.emissiveTexture == null) { logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.emissiveTexture=null", 3); } else { logger.RaiseVerbose("GLTFExporter.Material | babylonStandardMaterial.emissiveTexture.name=" + babylonStandardMaterial.emissiveTexture.name, 3); } #endregion // -------------------------------- // --------- gltfMaterial --------- // -------------------------------- logger.RaiseMessage("GLTFExporter.Material | create gltfMaterial", 2); gltfMaterial = new GLTFMaterial { name = name }; gltfMaterial.id = babylonMaterial.id; gltfMaterial.index = gltf.MaterialsList.Count; gltf.MaterialsList.Add(gltfMaterial); // Alpha string alphaMode; float? alphaCutoff; getAlphaMode(babylonStandardMaterial, out alphaMode, out alphaCutoff); gltfMaterial.alphaMode = alphaMode; gltfMaterial.alphaCutoff = alphaCutoff; // DoubleSided gltfMaterial.doubleSided = !babylonMaterial.backFaceCulling; // Normal gltfMaterial.normalTexture = ExportTexture(babylonStandardMaterial.bumpTexture, gltf); // Occulison gltfMaterial.occlusionTexture = ExportTexture(babylonStandardMaterial.ambientTexture, gltf); // Emissive gltfMaterial.emissiveFactor = babylonStandardMaterial.emissive; // linkEmissiveWithDiffuse attribute doesn't have an equivalent in gltf format // When true, the emissive texture needs to be manually multiplied with diffuse texture // Otherwise, the emissive texture is assumed to be already pre-multiplied if (babylonStandardMaterial.linkEmissiveWithDiffuse) { // Even when no emissive texture is provided, the self illumination value needs to be multiplied to the diffuse texture in order to get the pre-multiplied emissive (texture) if (babylonStandardMaterial.emissiveTexture != null || babylonStandardMaterial.selfIllum > 0) { // Default emissive is the raw value of the self illumination // It is not the babylon emissive value which is already pre-multiplied with diffuse color float[] defaultEmissive = new float[] { 1, 1, 1 }.Multiply(babylonStandardMaterial.selfIllum); gltfMaterial.emissiveTexture = ExportEmissiveTexture(babylonStandardMaterial, gltf, defaultEmissive, babylonStandardMaterial.diffuse); } } else { gltfMaterial.emissiveTexture = ExportTexture(babylonStandardMaterial.emissiveTexture, gltf); } // Constraints if (gltfMaterial.emissiveTexture != null) { gltfMaterial.emissiveFactor = new[] { 1.0f, 1.0f, 1.0f }; } // -------------------------------- // --- gltfPbrMetallicRoughness --- // -------------------------------- logger.RaiseMessage("GLTFExporter.Material | create gltfPbrMetallicRoughness", 2); var gltfPbrMetallicRoughness = new GLTFPBRMetallicRoughness(); gltfMaterial.pbrMetallicRoughness = gltfPbrMetallicRoughness; // --- Global --- // Eye Ball correction to limit overall brightness from std to PBR. // This only impacts the factors. var correctedDiffuse = new BabylonColor3(babylonStandardMaterial.diffuse).scale(0.5f); SpecularGlossiness _specularGlossiness = new SpecularGlossiness { diffuse = correctedDiffuse, opacity = babylonMaterial.alpha, specular = new BabylonColor3(babylonStandardMaterial.specular), glossiness = babylonStandardMaterial.specularPower / 256 }; MetallicRoughness _metallicRoughness = ConvertToMetallicRoughness(_specularGlossiness, true); // Base color gltfPbrMetallicRoughness.baseColorFactor = new float[4] { _metallicRoughness.baseColor.r, _metallicRoughness.baseColor.g, _metallicRoughness.baseColor.b, _metallicRoughness.opacity }; // Metallic roughness gltfPbrMetallicRoughness.metallicFactor = _metallicRoughness.metallic; gltfPbrMetallicRoughness.roughnessFactor = _metallicRoughness.roughness; // --- Textures --- var babylonTexture = babylonStandardMaterial.diffuseTexture != null ? babylonStandardMaterial.diffuseTexture : babylonStandardMaterial.specularTexture != null ? babylonStandardMaterial.specularTexture : babylonStandardMaterial.opacityTexture != null ? babylonStandardMaterial.opacityTexture : null; if (babylonTexture != null) { //Check if the texture already exist var _key = SetStandText(babylonStandardMaterial); if (GetStandTextInfo(_key) != null) { var _pairBCMR = GetStandTextInfo(_key); gltfPbrMetallicRoughness.baseColorTexture = _pairBCMR.baseColor; gltfPbrMetallicRoughness.metallicRoughnessTexture = _pairBCMR.metallicRoughness; } else { bool isAlphaInTexture = (isTextureOk(babylonStandardMaterial.diffuseTexture) && babylonStandardMaterial.diffuseTexture.hasAlpha) || isTextureOk(babylonStandardMaterial.opacityTexture); Bitmap baseColorBitmap = null; Bitmap metallicRoughnessBitmap = null; GLTFTextureInfo textureInfoBC = new GLTFTextureInfo(); GLTFTextureInfo textureInfoMR = new GLTFTextureInfo(); if (exportParameters.writeTextures) { // Diffuse Bitmap diffuseBitmap = null; if (babylonStandardMaterial.diffuseTexture != null) { diffuseBitmap = TextureUtilities.LoadTexture(babylonStandardMaterial.diffuseTexture.originalPath, logger); } // Specular Bitmap specularBitmap = null; if (babylonStandardMaterial.specularTexture != null) { if (babylonStandardMaterial.specularTexture.bitmap != null) { // Specular color map has been computed by the exporter specularBitmap = babylonStandardMaterial.specularTexture.bitmap; } else { // Specular color map is straight input specularBitmap = TextureUtilities.LoadTexture(babylonStandardMaterial.specularTexture.originalPath, logger); } } // Opacity / Alpha / Transparency Bitmap opacityBitmap = null; if ((babylonStandardMaterial.diffuseTexture == null || babylonStandardMaterial.diffuseTexture.hasAlpha == false) && babylonStandardMaterial.opacityTexture != null) { opacityBitmap = TextureUtilities.LoadTexture(babylonStandardMaterial.opacityTexture.originalPath, logger); } if (diffuseBitmap != null || specularBitmap != null || opacityBitmap != null) { // Retreive dimensions int width = 0; int height = 0; var haveSameDimensions = TextureUtilities.GetMinimalBitmapDimensions(out width, out height, diffuseBitmap, specularBitmap, opacityBitmap); if (!haveSameDimensions) { logger.RaiseError("Diffuse, specular and opacity maps should have same dimensions", 2); } // Create baseColor+alpha and metallic+roughness maps baseColorBitmap = new Bitmap(width, height); metallicRoughnessBitmap = new Bitmap(width, height); for (int x = 0; x < width; x++) { for (int y = 0; y < height; y++) { SpecularGlossiness specularGlossinessTexture = new SpecularGlossiness { diffuse = diffuseBitmap != null ? new BabylonColor3(diffuseBitmap.GetPixel(x, y)) : _specularGlossiness.diffuse, opacity = diffuseBitmap != null && babylonStandardMaterial.diffuseTexture.hasAlpha ? diffuseBitmap.GetPixel(x, y).A / 255.0f : opacityBitmap != null && babylonStandardMaterial.opacityTexture.getAlphaFromRGB ? opacityBitmap.GetPixel(x, y).R / 255.0f : opacityBitmap != null && babylonStandardMaterial.opacityTexture.getAlphaFromRGB == false?opacityBitmap.GetPixel(x, y).A / 255.0f : _specularGlossiness.opacity, specular = specularBitmap != null ? new BabylonColor3(specularBitmap.GetPixel(x, y)) : _specularGlossiness.specular, glossiness = babylonStandardMaterial.useGlossinessFromSpecularMapAlpha && specularBitmap != null?specularBitmap.GetPixel(x, y).A / 255.0f : _specularGlossiness.glossiness }; var displayPrints = x == width / 2 && y == height / 2; MetallicRoughness metallicRoughnessTexture = ConvertToMetallicRoughness(specularGlossinessTexture, displayPrints); Color colorBase = Color.FromArgb( (int)(metallicRoughnessTexture.opacity * 255), (int)(metallicRoughnessTexture.baseColor.r * 255), (int)(metallicRoughnessTexture.baseColor.g * 255), (int)(metallicRoughnessTexture.baseColor.b * 255) ); baseColorBitmap.SetPixel(x, y, colorBase); // The metalness values are sampled from the B channel. // The roughness values are sampled from the G channel. // These values are linear. If other channels are present (R or A), they are ignored for metallic-roughness calculations. Color colorMetallicRoughness = Color.FromArgb( 0, (int)(metallicRoughnessTexture.roughness * 255), (int)(metallicRoughnessTexture.metallic * 255) ); metallicRoughnessBitmap.SetPixel(x, y, colorMetallicRoughness); } } } } //export textures if (baseColorBitmap != null || babylonTexture.bitmap != null) { textureInfoBC = ExportBitmapTexture(gltf, babylonTexture, baseColorBitmap); gltfPbrMetallicRoughness.baseColorTexture = textureInfoBC; } if (isTextureOk(babylonStandardMaterial.specularTexture)) { textureInfoMR = ExportBitmapTexture(gltf, babylonTexture, metallicRoughnessBitmap); gltfPbrMetallicRoughness.metallicRoughnessTexture = textureInfoMR; } //register the texture AddStandText(_key, textureInfoBC, textureInfoMR); } // Constraints if (gltfPbrMetallicRoughness.baseColorTexture != null) { gltfPbrMetallicRoughness.baseColorFactor = new[] { 1.0f, 1.0f, 1.0f, 1.0f }; } if (gltfPbrMetallicRoughness.metallicRoughnessTexture != null) { gltfPbrMetallicRoughness.metallicFactor = 1.0f; gltfPbrMetallicRoughness.roughnessFactor = 1.0f; } } } else if (babylonMaterial.GetType() == typeof(BabylonPBRMetallicRoughnessMaterial)) { var babylonPBRMetallicRoughnessMaterial = babylonMaterial as BabylonPBRMetallicRoughnessMaterial; // --- prints --- #region prints logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial data", 2); logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial.alpha=" + babylonMaterial.alpha, 3); logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial.alphaMode=" + babylonMaterial.alphaMode, 3); logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial.backFaceCulling=" + babylonMaterial.backFaceCulling, 3); logger.RaiseVerbose("GLTFExporter.Material | babylonMaterial.wireframe=" + babylonMaterial.wireframe, 3); // Global logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.maxSimultaneousLights=" + babylonPBRMetallicRoughnessMaterial.maxSimultaneousLights, 3); logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.disableLighting=" + babylonPBRMetallicRoughnessMaterial.disableLighting, 3); logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.alphaCutOff=" + babylonPBRMetallicRoughnessMaterial.alphaCutOff, 3); logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.transparencyMode=" + babylonPBRMetallicRoughnessMaterial.transparencyMode, 3); logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.doubleSided=" + babylonPBRMetallicRoughnessMaterial.doubleSided, 3); // Base color logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.baseColor.Length=" + babylonPBRMetallicRoughnessMaterial.baseColor.Length, 3); for (int i = 0; i < babylonPBRMetallicRoughnessMaterial.baseColor.Length; i++) { logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.baseColor[" + i + "]=" + babylonPBRMetallicRoughnessMaterial.baseColor[i], 3); } if (babylonPBRMetallicRoughnessMaterial.baseTexture == null) { logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.baseTexture=null", 3); } // Metallic+roughness logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.metallic=" + babylonPBRMetallicRoughnessMaterial.metallic, 3); logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.roughness=" + babylonPBRMetallicRoughnessMaterial.roughness, 3); if (babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture == null) { logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture=null", 3); } // Normal / bump if (babylonPBRMetallicRoughnessMaterial.normalTexture == null) { logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.normalTexture=null", 3); } logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.invertNormalMapX=" + babylonPBRMetallicRoughnessMaterial.invertNormalMapX, 3); logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.invertNormalMapY=" + babylonPBRMetallicRoughnessMaterial.invertNormalMapY, 3); // Emissive for (int i = 0; i < babylonPBRMetallicRoughnessMaterial.emissive.Length; i++) { logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.emissiveColor[" + i + "]=" + babylonPBRMetallicRoughnessMaterial.emissive[i], 3); } if (babylonPBRMetallicRoughnessMaterial.emissiveTexture == null) { logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.emissiveTexture=null", 3); } // Ambient occlusion logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.occlusionStrength=" + babylonPBRMetallicRoughnessMaterial.occlusionStrength, 3); if (babylonPBRMetallicRoughnessMaterial.occlusionTexture == null) { logger.RaiseVerbose("GLTFExporter.Material | babylonPBRMetallicRoughnessMaterial.occlusionTexture=null", 3); } #endregion // -------------------------------- // --------- gltfMaterial --------- // -------------------------------- logger.RaiseMessage("GLTFExporter.Material | create gltfMaterial", 2); gltfMaterial = new GLTFMaterial { name = name }; gltfMaterial.id = babylonMaterial.id; gltfMaterial.index = gltf.MaterialsList.Count; gltf.MaterialsList.Add(gltfMaterial); // Alpha string alphaMode; float? alphaCutoff; getAlphaMode(babylonPBRMetallicRoughnessMaterial, out alphaMode, out alphaCutoff); gltfMaterial.alphaMode = alphaMode; gltfMaterial.alphaCutoff = alphaCutoff; // DoubleSided gltfMaterial.doubleSided = babylonPBRMetallicRoughnessMaterial.doubleSided; // Normal gltfMaterial.normalTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.normalTexture, gltf); // Occlusion if (babylonPBRMetallicRoughnessMaterial.occlusionTexture != null) { if (babylonPBRMetallicRoughnessMaterial.occlusionTexture.bitmap != null) { // ORM texture has been merged manually by the exporter // Occlusion is defined as well as metallic and/or roughness logger.RaiseVerbose("Occlusion is defined as well as metallic and/or roughness", 2); gltfMaterial.occlusionTexture = ExportBitmapTexture(gltf, babylonPBRMetallicRoughnessMaterial.occlusionTexture); } else { // ORM texture was already merged or only occlusion is defined logger.RaiseVerbose("ORM texture was already merged or only occlusion is defined", 2); gltfMaterial.occlusionTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.occlusionTexture, gltf); } } // Emissive gltfMaterial.emissiveFactor = babylonPBRMetallicRoughnessMaterial.emissive; gltfMaterial.emissiveTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.emissiveTexture, gltf); // -------------------------------- // --- gltfPbrMetallicRoughness --- // -------------------------------- logger.RaiseMessage("GLTFExporter.Material | create gltfPbrMetallicRoughness", 2); var gltfPbrMetallicRoughness = new GLTFPBRMetallicRoughness(); gltfMaterial.pbrMetallicRoughness = gltfPbrMetallicRoughness; // --- Global --- // Base color gltfPbrMetallicRoughness.baseColorFactor = new float[4] { babylonPBRMetallicRoughnessMaterial.baseColor[0], babylonPBRMetallicRoughnessMaterial.baseColor[1], babylonPBRMetallicRoughnessMaterial.baseColor[2], babylonPBRMetallicRoughnessMaterial.alpha }; if (babylonPBRMetallicRoughnessMaterial.baseTexture != null) { if (babylonPBRMetallicRoughnessMaterial.baseTexture.bitmap != null) { gltfPbrMetallicRoughness.baseColorTexture = ExportBitmapTexture(gltf, babylonPBRMetallicRoughnessMaterial.baseTexture); } else { gltfPbrMetallicRoughness.baseColorTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.baseTexture, gltf); } } // Metallic roughness gltfPbrMetallicRoughness.metallicFactor = babylonPBRMetallicRoughnessMaterial.metallic; gltfPbrMetallicRoughness.roughnessFactor = babylonPBRMetallicRoughnessMaterial.roughness; if (babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture != null) { if (babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture == babylonPBRMetallicRoughnessMaterial.occlusionTexture) { // Occlusion is defined as well as metallic and/or roughness // Use same texture logger.RaiseVerbose("Occlusion is defined as well as metallic and/or roughness", 2); gltfPbrMetallicRoughness.metallicRoughnessTexture = gltfMaterial.occlusionTexture; } else { // Occlusion is not defined, only metallic and/or roughness logger.RaiseVerbose("Occlusion is not defined, only metallic and/or roughness", 2); if (babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture.bitmap != null) { // Metallic & roughness texture has been merged manually by the exporter // Write bitmap file logger.RaiseVerbose("Metallic & roughness texture has been merged manually by the exporter", 2); gltfPbrMetallicRoughness.metallicRoughnessTexture = ExportBitmapTexture(gltf, babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture); } else { // Metallic & roughness texture was already merged // Copy file logger.RaiseVerbose("Metallic & roughness texture was already merged", 2); gltfPbrMetallicRoughness.metallicRoughnessTexture = ExportTexture(babylonPBRMetallicRoughnessMaterial.metallicRoughnessTexture, gltf); } } } } else { logger.RaiseWarning("GLTFExporter.Material | Unsupported material type: " + babylonMaterial.GetType(), 2); } if (gltfMaterial != null && babylonMaterial.isUnlit) { // Add Unlit extension if (!exportParameters.enableKHRMaterialsUnlit) { logger.RaiseWarning("GLTFExporter.Material | KHR_materials_unlit has not been enabled for export!", 2); } else { if (gltfMaterial.extensions == null) { gltfMaterial.extensions = new GLTFExtensions(); } if (gltf.extensionsUsed == null) { gltf.extensionsUsed = new System.Collections.Generic.List <string>(); } if (!gltf.extensionsUsed.Contains("KHR_materials_unlit")) { gltf.extensionsUsed.Add("KHR_materials_unlit"); } gltfMaterial.extensions["KHR_materials_unlit"] = new object(); } } ExportGLTFExtension(babylonMaterial, ref gltfMaterial, gltf); }
private void CopyGltfTexture(string sourcePath, string destPath) { TextureUtilities.CopyTexture(sourcePath, destPath, exportParameters.txtQuality, logger); }
public void Save(Stream destination, ObjectDatabase objectDatabase, TextureDatabase textureDatabase, BoneDatabase boneDatabase, bool leaveOpen = false) { if (objectDatabase != null) { foreach (var mesh in Meshes) { mesh.ID = objectDatabase.GetMesh(mesh.Name)?.ID ?? mesh.ID; } } if (boneDatabase != null) { string fileName = (destination is FileStream fileStream) ? Path.GetFileName(fileStream.Name) : string.Empty; // Assume we are exporting in game's style var skeleton = boneDatabase.Skeletons.FirstOrDefault(x => fileName.StartsWith(x.Name, StringComparison.OrdinalIgnoreCase)); // If we couldn't find it, default to CMN skeleton if (skeleton == null) { skeleton = boneDatabase.Skeletons.FirstOrDefault(x => x.Name.Equals("CMN", StringComparison.OrdinalIgnoreCase)); } // Still?? Then default to the first skeleton (this is unlikely to happen though) if (skeleton == null) { skeleton = boneDatabase.Skeletons[0]; } // Pretty much impossible to miss if (skeleton != null) { foreach (var skin in Meshes.Where(x => x.Skin != null).Select(x => x.Skin)) { foreach (var bone in skin.Bones) { int index = skin.ExData?.BoneNames?.FindIndex(x => x.Equals(bone.Name, StringComparison.OrdinalIgnoreCase)) ?? -1; if (index == -1) { index = skeleton.BoneNames1.FindIndex(x => x.Equals(bone.Name, StringComparison.OrdinalIgnoreCase)); } else { index = 0x8000 | index; } if (index != -1) { // Before we do this, fix the child bones foreach (var childBone in skin.Bones.Where(x => x.ParentID.Equals(bone.ID))) { childBone.ParentID = index; } // Now replace the ID bone.ID = index; } else { Debug.WriteLine($"Model.Save: Bone wasn't found in bone database or ex-data: {bone.Name}"); } } } } } if (textureDatabase != null && TextureSet != null) { var newIDs = new List <int>(TextureSet.Textures.Count); int currentID = textureDatabase.Textures.Max(x => x.ID) + 1; foreach (var texture in TextureSet.Textures) { var textureEntry = !string.IsNullOrEmpty(texture.Name) ? textureDatabase.GetTexture(texture.Name) : textureDatabase.GetTexture(texture.ID); if (textureEntry == null) { textureDatabase.Textures.Add(textureEntry = new TextureEntry { ID = currentID++, Name = texture.Name ?? $"Texture{currentID}", }); } newIDs.Add(textureEntry.ID); } if (!newIDs.SequenceEqual(TextureIDs)) { TextureUtilities.ReAssignTextureIDs(this, newIDs); } } Save(destination, leaveOpen); }