/// <summary> /// Adds geometry elements. /// </summary> /// <param name="root">Root element.</param> /// <param name="dfMesh">DFMesh.</param> private void AddGeometry(ref _daeElement root, ref DFMesh dfMesh) { _daeElement geomLib = root.add("library_geometries"); _daeElement geom = geomLib.add("geometry"); string geomId = "model" + dfMesh.ObjectId.ToString(); geom.setAttribute("id", geomId); _daeElement mesh = geom.add("mesh"); // Get source vertex data List <float> posArray = new List <float>(dfMesh.TotalVertices * 3); List <float> normalArray = new List <float>(dfMesh.TotalVertices * 3); List <float> uvArray = new List <float>(dfMesh.TotalVertices * 2); foreach (var subMesh in dfMesh.SubMeshes) { // Get texture dimensions for this submesh string archivePath = Path.Combine(arena2Path, TextureFile.IndexToFileName(subMesh.TextureArchive)); System.Drawing.Size sz = TextureFile.QuickSize(archivePath, subMesh.TextureRecord); // Collect vertex information for every plane in this submesh foreach (var plane in subMesh.Planes) { foreach (var point in plane.Points) { // Add position data posArray.Add(point.X); posArray.Add(-point.Y); posArray.Add(-point.Z); // Add normal data normalArray.Add(point.NX); normalArray.Add(-point.NY); normalArray.Add(-point.NZ); // Add UV data uvArray.Add(point.U / (float)sz.Width); uvArray.Add(-(point.V / (float)sz.Height)); } } } // Add positions, normals, and texture coordinates AddSource(ref mesh, geomId + "-positions", "X Y Z", ref posArray); AddSource(ref mesh, geomId + "-normals", "X Y Z", ref normalArray); AddSource(ref mesh, geomId + "-uv", "S T", ref uvArray); // Add <vertices> element _daeElement vertices = mesh.add("vertices"); vertices.setAttribute("id", geomId + "-vertices"); _daeElement verticesInput = vertices.add("input"); verticesInput.setAttribute("semantic", "POSITION"); verticesInput.setAttribute("source", MakeUriRef(geomId + "-positions")); // Add triangle indices for each submesh uint vertexCount = 0; foreach (var subMesh in dfMesh.SubMeshes) { // Loop through all planes in this submesh List <uint> indexArray = new List <uint>(subMesh.TotalTriangles * (3 * 3)); foreach (var plane in subMesh.Planes) { // Every DFPlane is a triangle fan radiating from point 0 uint sharedPoint = vertexCount++; // Index remaining points. There are (plane.Points.Length - 2) triangles in every plane for (int tri = 0; tri < plane.Points.Length - 2; tri++) { // Position, Normal, UV index for shared point indexArray.Add(sharedPoint); indexArray.Add(sharedPoint); indexArray.Add(sharedPoint); // Position, Normal, UV index for vertexCount indexArray.Add(vertexCount); indexArray.Add(vertexCount); indexArray.Add(vertexCount); // Position, Normal, UV index for vertexCount + 1 indexArray.Add(vertexCount + 1); indexArray.Add(vertexCount + 1); indexArray.Add(vertexCount + 1); // Increment vertexCount to next point in fan vertexCount++; } // Increment vertexCount to start of next fan in vertex buffer vertexCount++; } // Add <triangle> string materialName = MakeMaterialName(subMesh.TextureArchive, subMesh.TextureRecord); _daeElement triangles = mesh.add("triangles"); triangles.setAttribute("count", (indexArray.Count / (3 * 3)).ToString()); triangles.setAttribute("material", materialName + "-material"); AddInput(ref triangles, "VERTEX", geomId + "-vertices", 0); AddInput(ref triangles, "NORMAL", geomId + "-normals", 1); AddInput(ref triangles, "TEXCOORD", geomId + "-uv", 2); // Add <p> _daeElement p = triangles.add("p"); p.setCharData(UIntArrayToString(ref indexArray)); } }
private void CacheRecordSizesAndFrames(int textureArchive) { // Open texture file string path = Path.Combine(DaggerfallUnity.Instance.Arena2Path, TextureFile.IndexToFileName(textureArchive)); TextureFile textureFile = new TextureFile(path, FileUsage.UseMemory, true); // Cache size and scale for each record recordSizes = new Vector2[textureFile.RecordCount]; recordFrames = new int[textureFile.RecordCount]; for (int i = 0; i < textureFile.RecordCount; i++) { // Get size and scale of this texture DFSize size = textureFile.GetSize(i); DFSize scale = textureFile.GetScale(i); // Set start size Vector2 startSize; startSize.x = size.Width; startSize.y = size.Height; // Apply scale Vector2 finalSize; int xChange = (int)(size.Width * (scale.Width / BlocksFile.ScaleDivisor)); int yChange = (int)(size.Height * (scale.Height / BlocksFile.ScaleDivisor)); finalSize.x = (size.Width + xChange); finalSize.y = (size.Height + yChange); // Set optional scale TextureReplacement.SetBillboardScale(textureArchive, i, ref finalSize); // Store final size and frame count recordSizes[i] = finalSize * MeshReader.GlobalScale; recordFrames[i] = textureFile.GetFrameCount(i); } }
/// <summary> /// Gets inventory/equip image for specified item. /// Image will be cached based on material and hand for faster subsequent fetches. /// Animated item images do not support dyes. /// </summary> /// <param name="item">Item to fetch image for.</param> /// <param name="removeMask">Removes mask index (e.g. around helmets) from final image.</param> /// <param name="forPaperDoll">Image is for paper doll.</param> /// <param name="allowAnimation">Read animated textures.</param> /// <returns>ImageData.</returns> public ImageData GetItemImage(DaggerfallUnityItem item, bool removeMask = false, bool forPaperDoll = false, bool allowAnimation = false) { // Get colour int color = (int)item.dyeColor; // Get archive and record indices int archive = item.InventoryTextureArchive; int record = item.InventoryTextureRecord; // Paper doll handling if (forPaperDoll) { // 1H Weapons in right hand need record + 1 if (item.ItemGroup == ItemGroups.Weapons && item.EquipSlot == EquipSlots.RightHand) { if (ItemEquipTable.GetItemHands(item) == ItemHands.Either) { record += 1; } } } else { // Katanas need +1 for inventory image as they use right-hand image instead of left if (item.IsOfTemplate(ItemGroups.Weapons, (int)Weapons.Katana)) { record += 1; } } // Use world texture archive if inventory texture not set // Examples are gold pieces and wayrest painting if (archive == 0 && record == 0) { archive = item.ItemTemplate.worldTextureArchive; record = item.ItemTemplate.worldTextureRecord; } // Get unique key int key = MakeImageKey(color, archive, record, removeMask); // Get existing icon if in cache if (itemImages.ContainsKey(key)) { return(itemImages[key]); } // Load image data string filename = TextureFile.IndexToFileName(archive); ImageData data = ImageReader.GetImageData(filename, record, 0, true, false, allowAnimation); if (data.type == ImageTypes.None) { throw new Exception("GetItemImage() could not load image data."); } // Fix items with known incorrect paper doll offsets if (archive == 237 && (record == 52 || record == 54)) { // "Short shirt" template index 202 variants 2 and 5 for human female data.offset = new DaggerfallConnect.Utility.DFPosition(237, 43); } Texture2D tex; if (!forPaperDoll && TextureReplacement.TryImportTexture(archive, record, 0, item.dyeColor, out tex)) { // Assign imported texture // Paperdoll is disabled for now data.texture = tex; } else { // Remove mask if requested if (removeMask) { data.dfBitmap = ImageProcessing.ChangeMask(data.dfBitmap); } // Change dye or just update texture ItemGroups group = item.ItemGroup; DyeColors dye = (DyeColors)color; if (group == ItemGroups.Weapons || group == ItemGroups.Armor) { data = ChangeDye(data, dye, DyeTargets.WeaponsAndArmor); } else if (item.ItemGroup == ItemGroups.MensClothing || item.ItemGroup == ItemGroups.WomensClothing) { data = ChangeDye(data, dye, DyeTargets.Clothing); } else { ImageReader.UpdateTexture(ref data); } } // Add to cache itemImages.Add(key, data); return(data); }
/// <summary> /// Makes texture results for given material. /// </summary> /// <param name="material">Unity material.</param> /// <param name="archive">Texture archive.</param> /// <param name="record">Record index.</param> /// <returns>Results for the given material.</returns> public static GetTextureResults MakeResults(Material material, int archive, int record) { string path = Path.Combine(DaggerfallUnity.Instance.MaterialReader.TextureReader.Arena2Path, TextureFile.IndexToFileName(archive)); TextureFile textureFile = new TextureFile(path, FileUsage.UseMemory, true); return(new GetTextureResults() { albedoMap = GetTextureOrDefault(material, Uniforms.MainTex), normalMap = GetTextureOrDefault(material, Uniforms.BumpMap), emissionMap = GetTextureOrDefault(material, Uniforms.EmissionMap), singleRect = new Rect(0, 0, 1, 1), isWindow = ClimateSwaps.IsExteriorWindow(archive, record), isEmissive = material.HasProperty(Uniforms.EmissionMap), textureFile = textureFile }); }
private Material GetWindowMaterial(int key, WindowStyle windowStyle) { // Reverse key (input must be a MainKeyGroup key) int archive, record, frame; ReverseTextureKey(key, out archive, out record, out frame); // Determine new key group based on style int group; Color color; float intensity; switch (windowStyle) { case WindowStyle.Day: group = DayWindowKeyGroup; color = dfUnity.MaterialReader.DayWindowColor; intensity = dfUnity.MaterialReader.DayWindowIntensity; break; case WindowStyle.Night: group = NightWindowKeyGroup; color = dfUnity.MaterialReader.NightWindowColor; intensity = dfUnity.MaterialReader.NightWindowIntensity; break; case WindowStyle.Fog: group = FogWindowKeyGroup; color = dfUnity.MaterialReader.FogWindowColor; intensity = dfUnity.MaterialReader.FogWindowIntensity; break; case WindowStyle.Custom: group = CustomWindowKeyGroup; color = dfUnity.MaterialReader.CustomWindowColor; intensity = dfUnity.MaterialReader.CustomWindowIntensity; break; default: return(GetMaterial(archive, record)); // Just get base material with no processing } // Make new key based on group int newkey = MakeTextureKey((short)archive, (byte)record, (byte)0, group); // Check if material is already in cache CachedMaterial cm; if (materialDict.TryGetValue(newkey, out cm)) { // Return same if settings have not changed since last time if (cm.windowColor == color && cm.windowIntensity == intensity && cm.filterMode == MainFilterMode) { // Properties are the same return(cm.material); } else { materialDict.Remove(newkey); } } // Load texture file and get colour arrays textureReader.TextureFile.Load(Path.Combine(dfUnity.Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true); Color32 alpha = new Color32(0, 0, 0, (byte)(255f / intensity)); Color32[] diffuseColors, alphaColors; DFSize sz = textureReader.TextureFile.GetWindowColors32(record, color, alpha, out diffuseColors, out alphaColors); // Create diffuse texture Texture2D diffuse = new Texture2D(sz.Width, sz.Height, TextureFormat.RGBA32, MipMaps); diffuse.SetPixels32(diffuseColors); diffuse.Apply(true); // Create illumin texture Texture2D illumin = new Texture2D(sz.Width, sz.Height, TextureFormat.RGBA32, MipMaps); illumin.SetPixels32(alphaColors); illumin.Apply(true); // Create new material with a self-illuminating shader Shader shader = Shader.Find(DefaultSelfIlluminShaderName); Material material = new Material(shader); material.name = FormatName(archive, record); // Set material textures material.SetTexture("_MainTex", diffuse); material.SetTexture("_Illum", illumin); material.mainTexture.filterMode = MainFilterMode; // Cache this window material CachedMaterial newcm = new CachedMaterial() { key = newkey, keyGroup = group, singleRect = new Rect(0, 0, sz.Width, sz.Height), material = material, filterMode = MainFilterMode, isWindow = true, windowColor = color, windowIntensity = intensity, }; materialDict.Add(newkey, newcm); return(material); }
/// <summary> /// Gets terrain metallic gloss map texture array containing each terrain tile in a seperate array slice. /// </summary> /// <param name="archive">Archive index.</param> /// <param name="stayReadable">Texture should stay readable.</param> /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param> /// <returns>Texture2DArray or null</returns> public Texture2DArray GetTerrainMetallicGlossMapTextureArray( int archive, bool stayReadable = false, SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24) { Color32[] defaultMetallicGlossMap; // Load texture file and check count matches terrain tiles TextureFile textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true); int numSlices = 0; if (textureFile.RecordCount == 56) { numSlices = textureFile.RecordCount; } else { return(null); } Texture2DArray textureArray; int width; int height; // try to import first replacement texture for tile archive to determine width and height of replacement texture set (must be the same for all replacement textures for Texture2DArray) if (TextureReplacement.CustomMetallicGlossExist(archive, 0, 0)) { Texture2D metallicGlossMap = TextureReplacement.LoadCustomMetallicGloss(archive, 0, 0); width = metallicGlossMap.width; height = metallicGlossMap.height; } else { // create default texture array (1x1 texture) width = 1; height = 1; } textureArray = new Texture2DArray(width, height, numSlices, TextureFormat.ARGB32, MipMaps); defaultMetallicGlossMap = new Color32[width * height]; for (int i = 0; i < width * height; i++) { defaultMetallicGlossMap[i] = new Color32(0, 0, 0, 255); } // Rollout tiles into texture array for (int record = 0; record < textureFile.RecordCount; record++) { Texture2D metallicGlossMap; // Import custom texture(s) if (TextureReplacement.CustomMetallicGlossExist(archive, record, 0)) { metallicGlossMap = TextureReplacement.LoadCustomMetallicGloss(archive, record, 0); } else { //continue; metallicGlossMap = new Texture2D(width, height, TextureFormat.ARGB32, MipMaps); metallicGlossMap.SetPixels32(defaultMetallicGlossMap); } // enforce that all custom metallicgloss map textures have the same dimension (requirement of Texture2DArray) if ((metallicGlossMap.width != width) || (metallicGlossMap.height != height)) { return(null); } // Insert into texture array textureArray.SetPixels32(metallicGlossMap.GetPixels32(), record, 0); } textureArray.Apply(true, !stayReadable); // Change settings for these textures textureArray.wrapMode = TextureWrapMode.Clamp; textureArray.anisoLevel = 8; return(textureArray); }
/// <summary> /// Gets a custom material for a mobile billboard with textures and configuration imported from mods. /// </summary> /// <param name="archive">Archive index.</param> /// <param name="meshFilter">The MeshFilter of the billboard object.</param> /// <param name="importedTextures">All the imported textures for the archive.</param> /// <remarks> /// Seek the texture for the first frame of the first record. If found, it imports the entire archive. /// If this texture has an emission map the material is considered emissive and all emission maps are imported. /// </remarks> /// <returns>A material or null.</returns> public static Material GetMobileBillboardMaterial(int archive, MeshFilter meshFilter, ref MobileBillboardImportedTextures importedTextures) { if (!DaggerfallUnity.Settings.AssetInjection) { return(null); } Texture2D tex, emission; if (importedTextures.HasImportedTextures = LoadFromCacheOrImport(archive, 0, 0, true, true, out tex, out emission)) { string renderMode = null; // Read xml configuration XMLManager xml; if (XMLManager.TryReadXml(ImagesPath, string.Format("{0:000}", archive), out xml)) { xml.TryGetString("renderMode", out renderMode); importedTextures.IsEmissive = xml.GetBool("emission"); } // Make material Material material = MakeBillboardMaterial(renderMode); // Enable emission ToggleEmission(material, importedTextures.IsEmissive |= emission != null); // Load texture file to get record and frame count string fileName = TextureFile.IndexToFileName(archive); var textureFile = new TextureFile(Path.Combine(DaggerfallUnity.Instance.Arena2Path, fileName), FileUsage.UseMemory, true); // Import all textures in this archive importedTextures.Albedo = new Texture2D[textureFile.RecordCount][]; importedTextures.EmissionMaps = importedTextures.IsEmissive ? new Texture2D[textureFile.RecordCount][] : null; for (int record = 0; record < textureFile.RecordCount; record++) { int frames = textureFile.GetFrameCount(record); var frameTextures = new Texture2D[frames]; var frameEmissionMaps = importedTextures.IsEmissive ? new Texture2D[frames] : null; for (int frame = 0; frame < frames; frame++) { if (record != 0 || frame != 0) { LoadFromCacheOrImport(archive, record, frame, importedTextures.IsEmissive, true, out tex, out emission); } frameTextures[frame] = tex ?? ImageReader.GetTexture(fileName, record, frame, true); if (frameEmissionMaps != null) { frameEmissionMaps[frame] = emission ?? frameTextures[frame]; } } importedTextures.Albedo[record] = frameTextures; if (importedTextures.EmissionMaps != null) { importedTextures.EmissionMaps[record] = frameEmissionMaps; } } // Update UV map SetUv(meshFilter); return(material); } return(null); }
/// <summary> /// Gets specially packed tileset atlas for terrains. /// This needs to be improved to create each mip level manually to further reduce artifacts. /// </summary> /// <param name="archive">Archive index.</param> /// <param name="stayReadable">Texture should stay readable.</param> /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param> /// <returns></returns> public GetTextureResults GetTerrainTilesetTexture( int archive, bool stayReadable = false, SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24) { const int atlasDim = 2048; const int gutterSize = 32; GetTextureResults results = new GetTextureResults(); // Load texture file and check count matches terrain tiles TextureFile textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true); if (textureFile.RecordCount != 56) { return(results); } // Rollout tiles into atlas. // This is somewhat inefficient, but fortunately atlases only // need to be generated once and can be prepared offline where // startup time is critical. int x = 0, y = 0; Color32[] atlasColors = new Color32[atlasDim * atlasDim]; for (int record = 0; record < textureFile.RecordCount; record++) { // Create base image with gutter DFSize sz; Color32[] albedo = textureFile.GetColor32(record, 0, -1, gutterSize, out sz); // Wrap and clamp textures based on tile switch (record) { // Textures that do not tile in any direction case 5: case 7: case 10: case 12: case 15: case 17: case 20: case 22: case 25: case 27: case 30: case 32: case 34: case 35: case 36: case 37: case 38: case 39: case 40: case 41: case 42: case 43: case 44: case 45: case 47: case 48: case 49: case 50: case 51: case 52: case 53: case 55: ImageProcessing.ClampBorder(ref albedo, sz, gutterSize); break; // Textures that clamp horizontally and tile vertically case 6: case 11: case 16: case 21: case 26: case 31: ImageProcessing.WrapBorder(ref albedo, sz, gutterSize, false); ImageProcessing.ClampBorder(ref albedo, sz, gutterSize, true, false); break; // Textures that tile in all directions default: ImageProcessing.WrapBorder(ref albedo, sz, gutterSize); break; } // Create variants Color32[] rotate = ImageProcessing.RotateColors(ref albedo, sz.Width, sz.Height); Color32[] flip = ImageProcessing.FlipColors(ref albedo, sz.Width, sz.Height); Color32[] rotateFlip = ImageProcessing.RotateColors(ref flip, sz.Width, sz.Height); // Insert into atlas ImageProcessing.InsertColors(ref albedo, ref atlasColors, x, y, sz.Width, sz.Height, atlasDim, atlasDim); ImageProcessing.InsertColors(ref rotate, ref atlasColors, x + sz.Width, y, sz.Width, sz.Height, atlasDim, atlasDim); ImageProcessing.InsertColors(ref flip, ref atlasColors, x + sz.Width * 2, y, sz.Width, sz.Height, atlasDim, atlasDim); ImageProcessing.InsertColors(ref rotateFlip, ref atlasColors, x + sz.Width * 3, y, sz.Width, sz.Height, atlasDim, atlasDim); // Increment position x += sz.Width * 4; if (x >= atlasDim) { y += sz.Height; x = 0; } } // Create Texture2D Texture2D albedoAtlas = new Texture2D(atlasDim, atlasDim, ParseTextureFormat(nonAlphaFormat), MipMaps); albedoAtlas.SetPixels32(atlasColors); albedoAtlas.Apply(true, !stayReadable); // Change settings for these textures albedoAtlas.wrapMode = TextureWrapMode.Clamp; albedoAtlas.anisoLevel = 8; // Store results results.albedoMap = albedoAtlas; return(results); }
/// <summary> /// Gets terrain normal map texture array containing each terrain tile in a seperate array slice. /// </summary> /// <param name="archive">Archive index.</param> /// <param name="stayReadable">Texture should stay readable.</param> /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param> /// <returns>Texture2DArray or null</returns> public Texture2DArray GetTerrainNormalMapTextureArray( int archive, bool stayReadable = false, SupportedAlphaTextureFormats alphaFormat = SupportedAlphaTextureFormats.ARGB32) { // Load texture file and check count matches terrain tiles TextureFile textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true); int numSlices = 0; if (textureFile.RecordCount == 56) { numSlices = textureFile.RecordCount; } else { return(null); } Texture2DArray textureArray; if (!stayReadable && TryMakeTextureArrayCopyTexture(archive, numSlices, TextureMap.Normal, null, out textureArray)) { return(textureArray); } int width; int height; // try to import first replacement texture for tile archive to determine width and height of replacement texture set (must be the same for all replacement textures for Texture2DArray) Texture2D normalMap; if (TextureReplacement.TryImportTexture(archive, 0, 0, TextureMap.Normal, false, out normalMap)) { width = normalMap.width; height = normalMap.height; } else { return(null); } textureArray = new Texture2DArray(width, height, numSlices, ParseTextureFormat(alphaFormat), MipMaps); // Rollout tiles into texture array for (int record = 0; record < textureFile.RecordCount; record++) { // Import custom texture(s) if (!TextureReplacement.TryImportTexture(archive, record, 0, TextureMap.Normal, false, out normalMap)) { // if current texture does not exist Debug.LogErrorFormat("Terrain: imported archive {0} does not contain normal for record {1}.", archive, record); return(null); } // enforce that all custom normal map textures have the same dimension (requirement of Texture2DArray) if ((normalMap.width != width) || (normalMap.height != height)) { Debug.LogErrorFormat("Terrain: failed to inject normal maps for archive {0}, incorrect size at record {1}.", archive, record); return(null); } // Insert into texture array textureArray.SetPixels32(normalMap.GetPixels32(), record, 0); } textureArray.Apply(true, !stayReadable); // Change settings for these textures textureArray.wrapMode = TextureWrapMode.Clamp; textureArray.anisoLevel = 8; return(textureArray); }
/// <summary> /// Gets terrain metallic gloss map texture array containing each terrain tile in a seperate array slice. /// </summary> /// <param name="archive">Archive index.</param> /// <param name="stayReadable">Texture should stay readable.</param> /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param> /// <returns>Texture2DArray or null</returns> public Texture2DArray GetTerrainMetallicGlossMapTextureArray( int archive, bool stayReadable = false) { Color32[] defaultMetallicGlossMap; // Load texture file and check count matches terrain tiles TextureFile textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true); int numSlices = 0; if (textureFile.RecordCount == 56) { numSlices = textureFile.RecordCount; } else { return(null); } Texture2DArray textureArray; if (!stayReadable && TryMakeTextureArrayCopyTexture(archive, numSlices, TextureMap.MetallicGloss, new Color32(0, 0, 0, 255), out textureArray)) { return(textureArray); } int width; int height; // try to import first replacement texture for tile archive to determine width and height of replacement texture set (must be the same for all replacement textures for Texture2DArray) Texture2D metallicGlossMap; if (TextureReplacement.TryImportTexture(archive, 0, 0, TextureMap.MetallicGloss, false, out metallicGlossMap)) { width = metallicGlossMap.width; height = metallicGlossMap.height; } else { // create default texture array (1x1 texture) width = 1; height = 1; } textureArray = new Texture2DArray(width, height, numSlices, TextureFormat.ARGB32, MipMaps); defaultMetallicGlossMap = new Color32[width * height]; for (int i = 0; i < width * height; i++) { defaultMetallicGlossMap[i] = new Color32(0, 0, 0, 255); } // Rollout tiles into texture array for (int record = 0; record < textureFile.RecordCount; record++) { // Import custom texture(s) if (!TextureReplacement.TryImportTexture(archive, record, 0, TextureMap.MetallicGloss, false, out metallicGlossMap)) { metallicGlossMap = new Texture2D(width, height, TextureFormat.ARGB32, MipMaps); metallicGlossMap.SetPixels32(defaultMetallicGlossMap); } // enforce that all custom metallicgloss map textures have the same dimension (requirement of Texture2DArray) if ((metallicGlossMap.width != width) || (metallicGlossMap.height != height)) { Debug.LogErrorFormat("Terrain: failed to inject metallicgloss maps for archive {0}, incorrect size at record {1}.", archive, record); return(null); } // Insert into texture array textureArray.SetPixels32(metallicGlossMap.GetPixels32(), record, 0); } textureArray.Apply(true, !stayReadable); // Change settings for these textures textureArray.wrapMode = TextureWrapMode.Clamp; textureArray.anisoLevel = 8; return(textureArray); }
/// <summary> /// Gets terrain albedo texture array containing each terrain tile in a seperate array slice. /// </summary> /// <param name="archive">Archive index.</param> /// <param name="stayReadable">Texture should stay readable.</param> /// <returns>Texture2DArray or null</returns> public Texture2DArray GetTerrainAlbedoTextureArray( int archive, bool stayReadable = false) { // Load texture file and check count matches terrain tiles TextureFile textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true); int numSlices = 0; if (textureFile.RecordCount == 56) { numSlices = textureFile.RecordCount; } else { return(null); } Texture2DArray textureArray; if (!stayReadable && TryMakeTextureArrayCopyTexture(archive, numSlices, TextureMap.Albedo, null, out textureArray)) { return(textureArray); } Texture2D albedoMap; if (TextureReplacement.TryImportTexture(archive, 0, 0, out albedoMap)) { textureArray = new Texture2DArray(albedoMap.width, albedoMap.height, numSlices, TextureFormat.ARGB32, MipMaps); } else { textureArray = new Texture2DArray(textureFile.GetWidth(0), textureFile.GetWidth(1), numSlices, TextureFormat.ARGB32, MipMaps); } // Rollout tiles into texture array for (int record = 0; record < textureFile.RecordCount; record++) { Color32[] albedo; if (TextureReplacement.TryImportTexture(archive, record, 0, out albedoMap)) { // Import custom texture albedo = albedoMap.GetPixels32(); } else { // Create base image with gutter DFSize sz; albedo = textureFile.GetColor32(record, 0, -1, 0, out sz); } // Insert into texture array textureArray.SetPixels32(albedo, record, 0); } textureArray.Apply(true, !stayReadable); // Change settings for these textures textureArray.wrapMode = TextureWrapMode.Clamp; textureArray.anisoLevel = 8; return(textureArray); }
/// <summary> /// Loads all vertices for this DFMesh. /// </summary> /// <param name="model">Model object.</param> private void LoadVertices(ref ModelData model) { // Allocate vertex buffer model.Vertices = new VertexPositionNormalTextureBump[model.DFMesh.TotalVertices]; // Track min and max vectors for bounding box Vector3 min = new Vector3(0, 0, 0); Vector3 max = new Vector3(0, 0, 0); // Loop through all submeshes int vertexCount = 0; foreach (DFMesh.DFSubMesh dfSubMesh in model.DFMesh.SubMeshes) { // Get texture dimensions for this submesh string archivePath = Path.Combine(arena2Path, TextureFile.IndexToFileName(dfSubMesh.TextureArchive)); System.Drawing.Size sz = TextureFile.QuickSize(archivePath, dfSubMesh.TextureRecord); // Ensure texture dimensions are POW2 as TextureManager will be emitting POW2 textures int width = (PowerOfTwo.IsPowerOfTwo(sz.Width)) ? sz.Width : PowerOfTwo.NextPowerOfTwo(sz.Width); int height = (PowerOfTwo.IsPowerOfTwo(sz.Height)) ? sz.Height : PowerOfTwo.NextPowerOfTwo(sz.Height); Vector2 scale = new Vector2( (float)sz.Width / (float)width, (float)sz.Height / (float)height); // Loop through all planes in this submesh foreach (DFMesh.DFPlane dfPlane in dfSubMesh.Planes) { // Copy each point in this plane to vertex buffer foreach (DFMesh.DFPoint dfPoint in dfPlane.Points) { // Daggerfall uses a different axis layout than XNA. // The Y and Z axes should be inverted so the model is displayed correctly. // This also requires a change to winding order in LoadIndices(). Vector3 position = new Vector3(dfPoint.X, -dfPoint.Y, -dfPoint.Z) * GlobalScale; Vector3 normal = new Vector3(dfPoint.NX, -dfPoint.NY, -dfPoint.NZ); // Store vertex data model.Vertices[vertexCount].Position = position; model.Vertices[vertexCount].Normal = normal; model.Vertices[vertexCount].TextureCoordinate = new Vector2( (dfPoint.U / sz.Width) * scale.X, (dfPoint.V / sz.Height) * scale.Y); // Inrement count vertexCount++; // Compare min and max vectors if (position.X < min.X) { min.X = position.X; } if (position.Y < min.Y) { min.Y = position.Y; } if (position.Z < min.Z) { min.Z = position.Z; } if (position.X > max.X) { max.X = position.X; } if (position.Y > max.Y) { max.Y = position.Y; } if (position.Z > max.Z) { max.Z = position.Z; } } } } // Create bounding box model.BoundingBox = new BoundingBox(min, max); // Find model centre Vector3 modelCenter; modelCenter.X = min.X + (max.X - min.X) / 2; modelCenter.Y = min.Y + (max.Y - min.Y) / 2; modelCenter.Z = min.Z + (max.Z - min.Z) / 2; // Find model radius float modelRadius; modelRadius = Vector3.Distance(min, max) / 2; // Create bounding sphere model.BoundingSphere = new BoundingSphere(modelCenter, modelRadius); }
/// <summary> /// Gets Unity textures from Daggerfall texture with all options. /// Returns all supported texture maps for Standard shader in one call. /// </summary> /// <param name="settings">Get texture settings.</param> /// <param name="alphaTextureFormat">Alpha TextureFormat.</param> /// <param name="textureImport">Options for import of custom textures.</param> /// <returns>GetTextureResults.</returns> public GetTextureResults GetTexture2D( GetTextureSettings settings, SupportedAlphaTextureFormats alphaTextureFormat = SupportedAlphaTextureFormats.ARGB32, TextureImport textureImport = TextureImport.None) { GetTextureResults results = new GetTextureResults(); // Check if window or auto-emissive bool isWindow = ClimateSwaps.IsExteriorWindow(settings.archive, settings.record); bool isEmissive = (settings.autoEmission) ? IsEmissive(settings.archive, settings.record) : false; // Override readable flag when user has set preference in material reader if (DaggerfallUnity.Instance.MaterialReader.ReadableTextures) { settings.stayReadable = true; } // Disable mipmaps in retro mode if (DaggerfallUnity.Settings.RetroRenderingMode > 0 && !DaggerfallUnity.Settings.UseMipMapsInRetroMode) { mipMaps = false; } // Assign texture file TextureFile textureFile; if (settings.textureFile == null) { textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(settings.archive)), FileUsage.UseMemory, true); } else { textureFile = settings.textureFile; } // Get starting DFBitmap and albedo Color32 array DFSize sz; DFBitmap srcBitmap = textureFile.GetDFBitmap(settings.record, settings.frame); Color32[] albedoColors = textureFile.GetColor32(srcBitmap, settings.alphaIndex, settings.borderSize, out sz); // Sharpen source image if (settings.sharpen) { albedoColors = ImageProcessing.Sharpen(ref albedoColors, sz.Width, sz.Height); } // Dilate edges if (settings.borderSize > 0 && settings.dilate && !settings.copyToOppositeBorder) { ImageProcessing.DilateColors(ref albedoColors, sz); } // Copy to opposite border if (settings.borderSize > 0 && settings.copyToOppositeBorder) { ImageProcessing.WrapBorder(ref albedoColors, sz, settings.borderSize); } // Set albedo texture Texture2D albedoMap; if (!TextureReplacement.TryImportTexture(settings.archive, settings.record, settings.frame, TextureMap.Albedo, textureImport, !settings.stayReadable, out albedoMap)) { // Create albedo texture albedoMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps); albedoMap.SetPixels32(albedoColors); albedoMap.Apply(true, !settings.stayReadable); } // Adjust mipmap bias of albedo map when retro mode rendering is enabled if (albedoMap && DaggerfallUnity.Settings.RetroRenderingMode > 0) { albedoMap.mipMapBias = -0.75f; } // Set normal texture (always import normal if present on disk) Texture2D normalMap = null; bool normalMapImported = TextureReplacement.TryImportTexture(settings.archive, settings.record, settings.frame, TextureMap.Normal, textureImport, !settings.stayReadable, out normalMap); if (!normalMapImported && settings.createNormalMap && textureFile.SolidType == TextureFile.SolidTypes.None) { // Create normal texture - must be ARGB32 // Normal maps are bypassed for solid-colour textures Color32[] normalColors; normalColors = ImageProcessing.GetBumpMap(ref albedoColors, sz.Width, sz.Height); normalColors = ImageProcessing.ConvertBumpToNormals(ref normalColors, sz.Width, sz.Height, settings.normalStrength); normalMap = new Texture2D(sz.Width, sz.Height, TextureFormat.ARGB32, MipMaps); normalMap.SetPixels32(normalColors); normalMap.Apply(true, !settings.stayReadable); } // Import emission map or create basic emissive texture Texture2D emissionMap = null; bool resultEmissive = false; if (TextureReplacement.TryImportTexture(settings.archive, settings.record, settings.frame, TextureMap.Emission, textureImport, !settings.stayReadable, out emissionMap)) { // Always import emission if present on disk resultEmissive = true; } else { if (settings.createEmissionMap || (settings.autoEmission && isEmissive) && !isWindow) { // Just reuse albedo map for basic colour emission emissionMap = albedoMap; resultEmissive = true; } // Windows need special handling as only glass parts are emissive if ((settings.createEmissionMap || settings.autoEmissionForWindows) && isWindow) { // Create custom emission texture for glass area of windows Color32[] emissionColors = textureFile.GetWindowColors32(srcBitmap); emissionMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps); emissionMap.SetPixels32(emissionColors); emissionMap.Apply(true, !settings.stayReadable); resultEmissive = true; } // Lights need special handling as this archive contains a mix of emissive and non-emissive flats // This can cause problems with atlas packing due to mismatch between albedo and emissive texture counts if ((settings.createEmissionMap || settings.autoEmission) && settings.archive == LightsTextureArchive) { // For the unlit flats we create a null-emissive black texture if (!isEmissive) { Color32[] emissionColors = new Color32[albedoMap.width * albedoMap.height]; emissionMap = new Texture2D(albedoMap.width, albedoMap.height, ParseTextureFormat(alphaTextureFormat), MipMaps); emissionMap.SetPixels32(emissionColors); emissionMap.Apply(true, !settings.stayReadable); resultEmissive = true; } } } // Shrink UV rect to compensate for internal border float ru = 1f / sz.Width; float rv = 1f / sz.Height; results.singleRect = new Rect( settings.borderSize * ru, settings.borderSize * rv, (sz.Width - settings.borderSize * 2) * ru, (sz.Height - settings.borderSize * 2) * rv); // Store results results.albedoMap = albedoMap; results.normalMap = normalMap; results.emissionMap = emissionMap; results.isWindow = isWindow; results.isEmissive = resultEmissive; results.textureFile = textureFile; return(results); }
/// <summary> /// Gets Unity textures from Daggerfall texture with all options. /// Returns all supported texture maps for Standard shader in one call. /// </summary> /// <param name="settings">Get texture settings.</param> /// <param name="alphaTextureFormat">Alpha TextureFormat.</param> /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param> /// <returns>GetTextureResults.</returns> public GetTextureResults GetTexture2D( GetTextureSettings settings, SupportedAlphaTextureFormats alphaTextureFormat = SupportedAlphaTextureFormats.RGBA32, SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24) { GetTextureResults results = new GetTextureResults(); // Check if window or auto-emissive bool isWindow = ClimateSwaps.IsExteriorWindow(settings.archive, settings.record); bool isEmissive = (settings.autoEmission) ? IsEmissive(settings.archive, settings.record) : false; // Assign texture file TextureFile textureFile; if (settings.textureFile == null) { textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(settings.archive)), FileUsage.UseMemory, true); } else { textureFile = settings.textureFile; } // Get starting DFBitmap and albedo Color32 array DFSize sz; DFBitmap srcBitmap = textureFile.GetDFBitmap(settings.record, settings.frame); Color32[] albedoColors = textureFile.GetColor32(srcBitmap, settings.alphaIndex, settings.borderSize, out sz); // Sharpen source image if (settings.sharpen) { albedoColors = ImageProcessing.Sharpen(ref albedoColors, sz.Width, sz.Height); } // Dilate edges if (settings.borderSize > 0 && settings.dilate && !settings.copyToOppositeBorder) { ImageProcessing.DilateColors(ref albedoColors, sz); } // Copy to opposite border if (settings.borderSize > 0 && settings.copyToOppositeBorder) { ImageProcessing.WrapBorder(ref albedoColors, sz, settings.borderSize); } // Create albedo texture Texture2D albedoMap = null; if (settings.alphaIndex < 0) { albedoMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(nonAlphaFormat), MipMaps); } else { albedoMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps); } albedoMap.SetPixels32(albedoColors); albedoMap.Apply(true, !settings.stayReadable); // Create normal texture - must be ARGB32 // Normal maps are bypassed for solid-colour textures Texture2D normalMap = null; if (settings.createNormalMap && textureFile.SolidType == TextureFile.SolidTypes.None) { Color32[] normalColors; normalColors = ImageProcessing.GetBumpMap(ref albedoColors, sz.Width, sz.Height); normalColors = ImageProcessing.ConvertBumpToNormals(ref normalColors, sz.Width, sz.Height, settings.normalStrength); normalMap = new Texture2D(sz.Width, sz.Height, TextureFormat.ARGB32, MipMaps); normalMap.SetPixels32(normalColors); normalMap.Apply(true, !settings.stayReadable); } // Create basic emissive texture Texture2D emissionMap = null; bool resultEmissive = false; if (settings.createEmissionMap || (settings.autoEmission && isEmissive) && !isWindow) { // Just reuse albedo map for basic colour emission emissionMap = albedoMap; resultEmissive = true; } // Windows need special handling as only glass parts are emissive if ((settings.createEmissionMap || settings.autoEmissionForWindows) && isWindow) { // Create custom emission texture for glass area of windows Color32[] emissionColors = textureFile.GetWindowColors32(srcBitmap); emissionMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps); emissionMap.SetPixels32(emissionColors); emissionMap.Apply(true, !settings.stayReadable); resultEmissive = true; } // Lights need special handling as this archive contains a mix of emissive and non-emissive flats // This can cause problems with atlas packing due to mismatch between albedo and emissive texture counts if ((settings.createEmissionMap || settings.autoEmission) && settings.archive == LightsTextureArchive) { // For the unlit flats we create a null-emissive black texture if (!isEmissive) { Color32[] emissionColors = new Color32[sz.Width * sz.Height]; emissionMap = new Texture2D(sz.Width, sz.Height, ParseTextureFormat(alphaTextureFormat), MipMaps); emissionMap.SetPixels32(emissionColors); emissionMap.Apply(true, !settings.stayReadable); resultEmissive = true; } } // Shrink UV rect to compensate for internal border float ru = 1f / sz.Width; float rv = 1f / sz.Height; results.singleRect = new Rect( settings.borderSize * ru, settings.borderSize * rv, (sz.Width - settings.borderSize * 2) * ru, (sz.Height - settings.borderSize * 2) * rv); // Store results results.albedoMap = albedoMap; results.normalMap = normalMap; results.emissionMap = emissionMap; results.isWindow = isWindow; results.isEmissive = resultEmissive; results.textureFile = textureFile; return(results); }
/// <summary> /// Gets inventory/equip image for specified item. /// Image will be cached based on material and hand for faster subsequent fetches. /// </summary> /// <param name="item">Item to fetch image for.</param> /// <param name="removeMask">Removes mask index (e.g. around helmets) from final image.</param> /// <param name="forPaperDoll">Image is for paper doll.</param> /// <returns>ImageData.</returns> public ImageData GetItemImage(DaggerfallUnityItem item, bool removeMask = false, bool forPaperDoll = false) { // Get colour int color = (int)item.dyeColor; // Get archive and record indices int archive = item.InventoryTextureArchive; int record = item.InventoryTextureRecord; // Paper doll handling if (forPaperDoll) { // 1H Weapons in right hand need record + 1 if (item.ItemGroup == ItemGroups.Weapons && item.EquipSlot == EquipSlots.RightHand) { if (ItemEquipTable.GetItemHands(item) == ItemHands.Either) { record += 1; } } } // Get unique key int key = MakeImageKey(color, archive, record, removeMask); // Get existing icon if in cache if (itemImages.ContainsKey(key)) { return(itemImages[key]); } // Load image data string filename = TextureFile.IndexToFileName(archive); ImageData data = ImageReader.GetImageData(filename, record, 0, true, false); if (data.type == ImageTypes.None) { throw new Exception("GetItemImage() could not load image data."); } // Remove mask if requested if (removeMask) { data.dfBitmap = ImageProcessing.ChangeMask(data.dfBitmap); } // Change dye or just update texture ItemGroups group = item.ItemGroup; DyeColors dye = (DyeColors)color; if (group == ItemGroups.Weapons || group == ItemGroups.Armor) { data = ChangeDye(data, dye, DyeTargets.WeaponsAndArmor); } else if (item.ItemGroup == ItemGroups.MensClothing || item.ItemGroup == ItemGroups.WomensClothing) { data = ChangeDye(data, dye, DyeTargets.Clothing); } else { ImageReader.UpdateTexture(ref data); } // Add to cache itemImages.Add(key, data); return(data); }
/// <summary> /// Gets Texture2D atlas from Daggerfall texture archive. /// Every record and frame in the archive will be added to atlas. /// An array of rects will be returned with sub-texture rect for each record index and frame. /// Currently supports one archive per atlas. Super-atlas packing (multiple archives) is in the works. /// </summary> /// <param name="settings">Get texture settings.</param> /// <param name="alphaTextureFormat">Alpha TextureFormat.</param> /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param> /// <returns>GetTextureResults.</returns> public GetTextureResults GetTexture2DAtlas( GetTextureSettings settings, SupportedAlphaTextureFormats alphaTextureFormat = SupportedAlphaTextureFormats.RGBA32, SupportedNonAlphaTextureFormats nonAlphaFormat = SupportedNonAlphaTextureFormats.RGB24) { GetTextureResults results = new GetTextureResults(); // Individual textures must remain readable to pack into atlas bool stayReadable = settings.stayReadable; settings.stayReadable = true; // Assign texture file TextureFile textureFile; if (settings.textureFile == null) { textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(settings.archive)), FileUsage.UseMemory, true); settings.textureFile = textureFile; } else { textureFile = settings.textureFile; } // Create lists results.atlasSizes = new List <Vector2>(textureFile.RecordCount); results.atlasScales = new List <Vector2>(textureFile.RecordCount); results.atlasOffsets = new List <Vector2>(textureFile.RecordCount); results.atlasFrameCounts = new List <int>(textureFile.RecordCount); // Read every texture in archive bool hasNormalMaps = false; bool hasEmissionMaps = false; bool hasAnimation = false; List <Texture2D> albedoTextures = new List <Texture2D>(); List <Texture2D> normalTextures = new List <Texture2D>(); List <Texture2D> emissionTextures = new List <Texture2D>(); List <RecordIndex> indices = new List <RecordIndex>(); for (int record = 0; record < textureFile.RecordCount; record++) { // Get record index and frame count settings.record = record; int frames = textureFile.GetFrameCount(record); if (frames > 1) { hasAnimation = true; } // Get record information DFSize size = textureFile.GetSize(record); DFSize scale = textureFile.GetScale(record); DFPosition offset = textureFile.GetOffset(record); RecordIndex ri = new RecordIndex() { startIndex = albedoTextures.Count, frameCount = frames, width = size.Width, height = size.Height, }; indices.Add(ri); for (int frame = 0; frame < frames; frame++) { settings.frame = frame; GetTextureResults nextTextureResults = GetTexture2D(settings, alphaTextureFormat, nonAlphaFormat); albedoTextures.Add(nextTextureResults.albedoMap); if (nextTextureResults.normalMap != null) { normalTextures.Add(nextTextureResults.normalMap); hasNormalMaps = true; } if (nextTextureResults.emissionMap != null) { emissionTextures.Add(nextTextureResults.emissionMap); hasEmissionMaps = true; } } results.atlasSizes.Add(new Vector2(size.Width, size.Height)); results.atlasScales.Add(new Vector2(scale.Width, scale.Height)); results.atlasOffsets.Add(new Vector2(offset.X, offset.Y)); results.atlasFrameCounts.Add(frames); results.textureFile = textureFile; } // Pack albedo textures into atlas and get our rects Texture2D atlasAlbedoMap = new Texture2D(settings.atlasMaxSize, settings.atlasMaxSize, ParseTextureFormat(alphaTextureFormat), MipMaps); Rect[] rects = atlasAlbedoMap.PackTextures(albedoTextures.ToArray(), settings.atlasPadding, settings.atlasMaxSize, !stayReadable); // Pack normal textures into atlas Texture2D atlasNormalMap = null; if (hasNormalMaps) { // Normals must be ARGB32 atlasNormalMap = new Texture2D(settings.atlasMaxSize, settings.atlasMaxSize, TextureFormat.ARGB32, MipMaps); atlasNormalMap.PackTextures(normalTextures.ToArray(), settings.atlasPadding, settings.atlasMaxSize, !stayReadable); } // Pack emission textures into atlas // TODO: Change this as packing not consistent Texture2D atlasEmissionMap = null; if (hasEmissionMaps) { // Repacking to ensure correct mix of lit and unlit atlasEmissionMap = new Texture2D(settings.atlasMaxSize, settings.atlasMaxSize, ParseTextureFormat(alphaTextureFormat), MipMaps); atlasEmissionMap.PackTextures(emissionTextures.ToArray(), settings.atlasPadding, settings.atlasMaxSize, !stayReadable); } // Add to results if (results.atlasRects == null) { results.atlasRects = new List <Rect>(rects.Length); } if (results.atlasIndices == null) { results.atlasIndices = new List <RecordIndex>(indices.Count); } results.atlasRects.AddRange(rects); results.atlasIndices.AddRange(indices); // Shrink UV rect to compensate for internal border float ru = 1f / atlasAlbedoMap.width; float rv = 1f / atlasAlbedoMap.height; int finalBorder = settings.borderSize + settings.atlasShrinkUVs; for (int i = 0; i < results.atlasRects.Count; i++) { Rect rct = results.atlasRects[i]; rct.xMin += finalBorder * ru; rct.xMax -= finalBorder * ru; rct.yMin += finalBorder * rv; rct.yMax -= finalBorder * rv; results.atlasRects[i] = rct; } // Store results results.albedoMap = atlasAlbedoMap; results.normalMap = atlasNormalMap; results.emissionMap = atlasEmissionMap; results.isAtlasAnimated = hasAnimation; results.isEmissive = hasEmissionMaps; return(results); }
/// <summary> /// Gets terrain normal map texture array containing each terrain tile in a seperate array slice. /// </summary> /// <param name="archive">Archive index.</param> /// <param name="stayReadable">Texture should stay readable.</param> /// <param name="nonAlphaFormat">Non-alpha TextureFormat.</param> /// <returns>Texture2DArray or null</returns> public Texture2DArray GetTerrainNormalMapTextureArray( int archive, bool stayReadable = false, SupportedAlphaTextureFormats alphaFormat = SupportedAlphaTextureFormats.RGBA32) { // Load texture file and check count matches terrain tiles TextureFile textureFile = new TextureFile(Path.Combine(Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true); int numSlices = 0; if (textureFile.RecordCount == 56) { numSlices = textureFile.RecordCount; } else { return(null); } Texture2DArray textureArray; int width; int height; // try to import first replacement texture for tile archive to determine width and height of replacement texture set (must be the same for all replacement textures for Texture2DArray) if (TextureReplacement.CustomNormalExist(archive, 0, 0)) { Texture2D normalMap = TextureReplacement.LoadCustomNormal(archive, 0, 0); width = normalMap.width; height = normalMap.height; } else { return(null); } textureArray = new Texture2DArray(width, height, numSlices, TextureFormat.ARGB32, MipMaps); // Rollout tiles into texture array for (int record = 0; record < textureFile.RecordCount; record++) { Texture2D normalMap; // Import custom texture(s) if (TextureReplacement.CustomNormalExist(archive, record, 0)) { normalMap = TextureReplacement.LoadCustomNormal(archive, record, 0); } else // if current texture does not exist { return(null); } // enforce that all custom normal map textures have the same dimension (requirement of Texture2DArray) if ((normalMap.width != width) || (normalMap.height != height)) { return(null); } // Insert into texture array textureArray.SetPixels32(normalMap.GetPixels32(), record, 0); } textureArray.Apply(true, !stayReadable); // Change settings for these textures textureArray.wrapMode = TextureWrapMode.Clamp; textureArray.anisoLevel = 8; return(textureArray); }
private void DisplayAboutGUI() { EditorGUILayout.Space(); ShowCustomBillboardFoldout = GUILayoutHelper.Foldout(ShowCustomBillboardFoldout, new GUIContent("Custom"), () => { var propCustomArchive = Prop("customArchive"); var propCustomRecord = Prop("customRecord"); GUILayoutHelper.Indent(() => { propCustomArchive.intValue = EditorGUILayout.IntField(new GUIContent("Archive", "Set texture archive index (e.g. TEXTURE.210 is 210)"), propCustomArchive.intValue); propCustomRecord.intValue = EditorGUILayout.IntField(new GUIContent("Record", "Set texture record index (between 0-n)"), propCustomRecord.intValue); if (GUILayout.Button("Set Billboard Texture")) { try { dfBillboard.SetMaterial(propCustomArchive.intValue, propCustomRecord.intValue); } catch (Exception ex) { Debug.Log("Failed to set custom billboard texture. Exception: " + ex.Message); } } if (GUILayout.Button("Align To Surface")) { GameObjectHelper.AlignBillboardToGround(dfBillboard.gameObject, dfBillboard.Summary.Size, 4); } }); }); EditorGUILayout.Space(); ShowAboutBillboardFoldout = GUILayoutHelper.Foldout(ShowAboutBillboardFoldout, new GUIContent("About"), () => { GUILayoutHelper.Indent(() => { GUILayoutHelper.Horizontal(() => { EditorGUILayout.LabelField("File", GUILayout.Width(EditorGUIUtility.labelWidth - 4)); EditorGUILayout.SelectableLabel(TextureFile.IndexToFileName(dfBillboard.Summary.Archive), EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); }); GUILayoutHelper.Horizontal(() => { EditorGUILayout.LabelField("Index", GUILayout.Width(EditorGUIUtility.labelWidth - 4)); EditorGUILayout.SelectableLabel(dfBillboard.Summary.Record.ToString(), EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); }); //GUILayoutHelper.Horizontal(() => //{ // EditorGUILayout.LabelField("In Dungeon", GUILayout.Width(EditorGUIUtility.labelWidth - 4)); // EditorGUILayout.SelectableLabel(dfBillboard.Summary.InDungeon.ToString(), EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); //}); GUILayoutHelper.Horizontal(() => { EditorGUILayout.LabelField("Is Mobile", GUILayout.Width(EditorGUIUtility.labelWidth - 4)); EditorGUILayout.SelectableLabel(dfBillboard.Summary.IsMobile.ToString(), EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); }); if (dfBillboard.Summary.IsMobile) { GUILayoutHelper.Horizontal(() => { EditorGUILayout.LabelField("Flags", GUILayout.Width(EditorGUIUtility.labelWidth - 4)); EditorGUILayout.SelectableLabel(dfBillboard.Summary.Flags.ToString(), EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); }); GUILayoutHelper.Horizontal(() => { EditorGUILayout.LabelField("Type", GUILayout.Width(EditorGUIUtility.labelWidth - 4)); EditorGUILayout.SelectableLabel(dfBillboard.Summary.FixedEnemyType.ToString(), EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); }); } GUILayoutHelper.Horizontal(() => { EditorGUILayout.LabelField("Is Atlased", GUILayout.Width(EditorGUIUtility.labelWidth - 4)); EditorGUILayout.SelectableLabel(dfBillboard.Summary.AtlasedMaterial.ToString(), EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); }); GUILayoutHelper.Horizontal(() => { EditorGUILayout.LabelField("Is Animated", GUILayout.Width(EditorGUIUtility.labelWidth - 4)); EditorGUILayout.SelectableLabel(dfBillboard.Summary.AnimatedMaterial.ToString(), EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); }); GUILayoutHelper.Horizontal(() => { EditorGUILayout.LabelField("Current Frame", GUILayout.Width(EditorGUIUtility.labelWidth - 4)); EditorGUILayout.SelectableLabel(dfBillboard.Summary.CurrentFrame.ToString(), EditorStyles.textField, GUILayout.Height(EditorGUIUtility.singleLineHeight)); }); }); }); }
/// <summary> /// Gets Texture2D atlas from Daggerfall texture archive. /// Every record and frame in the archive will be added to atlas. /// An array of rects will be returned with sub-texture rect for each record index and frame. /// Used for non-tiling textures like flats and ground tiles. /// </summary> /// <param name="archive">Archive index to create atlas from.</param> /// <param name="alphaIndex">Index to receive transparent alpha.</param> /// <param name="padding">Number of pixels padding around each sub-texture.</param> /// <param name="maxAtlasSize">Max size of atlas.</param> /// <param name="rectsOut">Array of rects, one for each record sub-texture and frame.</param> /// <param name="indicesOut">Array of record indices into rect array, accounting for animation frames.</param> /// <param name="border">Number of pixels internal border around each texture.</param> /// <param name="dilate">Blend texture into surrounding empty pixels.</param> /// <param name="shrinkUVs">Number of extra pixels to shrink UV rect.</param> /// <param name="copyToOppositeBorder">Copy texture edges to opposite border. Requires border, will overwrite dilate.</param> /// <returns>Texture2D atlas or null.</returns> public Texture2D GetTexture2DAtlas( int archive, int alphaIndex, int padding, int maxAtlasSize, out Rect[] rectsOut, out RecordIndex[] indicesOut, out Vector2[] sizesOut, out Vector2[] scalesOut, out Vector2[] offsetsOut, int border, bool dilate, int shrinkUVs = 0, bool copyToOppositeBorder = false, bool makeNoLongerReadable = true) { // Ready check if (!ReadyCheck()) { rectsOut = null; indicesOut = null; sizesOut = null; scalesOut = null; offsetsOut = null; return(null); } // Load texture file textureFile.Load(Path.Combine(Arena2Path, TextureFile.IndexToFileName(archive)), FileUsage.UseMemory, true); // Read every texture in archive Rect rect; List <Texture2D> textures = new List <Texture2D>(); List <RecordIndex> indices = new List <RecordIndex>(); sizesOut = new Vector2[textureFile.RecordCount]; scalesOut = new Vector2[textureFile.RecordCount]; offsetsOut = new Vector2[textureFile.RecordCount]; for (int record = 0; record < textureFile.RecordCount; record++) { int frames = textureFile.GetFrameCount(record); DFSize size = textureFile.GetSize(record); DFSize scale = textureFile.GetScale(record); DFSize offset = textureFile.GetOffset(record); RecordIndex ri = new RecordIndex() { startIndex = textures.Count, frameCount = frames, width = size.Width, height = size.Height, }; indices.Add(ri); for (int frame = 0; frame < frames; frame++) { textures.Add(GetTexture2D(archive, record, frame, alphaIndex, out rect, border, dilate, copyToOppositeBorder, false)); } sizesOut[record] = new Vector2(size.Width, size.Height); scalesOut[record] = new Vector2(scale.Width, scale.Height); offsetsOut[record] = new Vector2(offset.Width, offset.Height); } // Pack textures into atlas Texture2D atlas = new Texture2D(maxAtlasSize, maxAtlasSize, TextureFormat.RGBA32, MipMaps); rectsOut = atlas.PackTextures(textures.ToArray(), padding, maxAtlasSize, makeNoLongerReadable); indicesOut = indices.ToArray(); // Shrink UV rect to compensate for internal border float ru = 1f / atlas.width; float rv = 1f / atlas.height; border += shrinkUVs; for (int i = 0; i < rectsOut.Length; i++) { Rect rct = rectsOut[i]; rct.xMin += border * ru; rct.xMax -= border * ru; rct.yMin += border * rv; rct.yMax -= border * rv; rectsOut[i] = rct; } return(atlas); }