/// <summary>
        /// Parse all Doodad instances and render them onto the ADT
        /// </summary>
        public void CreateDoodadInstances(ADTModel model)
        {
            var parent = GameObject.Find($"{model.MapId}_{model.X}_{model.Y}");

            if (parent == null)
            {
                throw new Exception($"Parent cannot be found: '{model.MapId}_{model.X}_{model.Y}'");
            }

            var doodadParent = new GameObject("doodads");

            doodadParent.transform.position = new Vector3((32 - model.Y) * WorldConstants.BlockSize, 0, (32 - model.X) * WorldConstants.BlockSize);
            doodadParent.transform.SetParent(parent.transform);

            //foreach (var doodad in model.DoodadInstances.Values)
            for (var i = 0; i < 5; ++i)
            {
                var doodad = model.DoodadInstances.Values.ToList()[i];
                if (!World.LoadedUniqueIds.Contains(doodad.UniqueId))
                {
                    World.LoadedUniqueIds.Add(doodad.UniqueId);
                    M2Loader.EnqueueDoodad(new M2QueueItem
                    {
                        FileDataId = doodad.FileDataId,
                        AdtParent  = doodadParent,
                        Position   = doodad.Position,
                        Rotation   = doodad.Rotation,
                        Scale      = new Vector3(doodad.Scale, doodad.Scale, doodad.Scale),
                        UniqueId   = doodad.UniqueId
                    });
                }
            }
        }
        public ActionResult Index()
        {
            ADTModel adtModel = new ADTModel
            {
                StatusMessage = ""
            };

            return(View("Index", adtModel));
        }
        public ActionResult Upload(string instanceUrl, string tenantId, string clientId, string secret)
        {
            ADTModel adtModel = new ADTModel
            {
                InstanceUrl = instanceUrl,
                TenantId    = tenantId,
                ClientId    = clientId,
                Secret      = secret
            };

            try
            {
                // login to Azure using the Environment variables mechanism of DefaultAzureCredential
                Environment.SetEnvironmentVariable("AZURE_CLIENT_ID", clientId);
                Environment.SetEnvironmentVariable("AZURE_TENANT_ID", tenantId);
                Environment.SetEnvironmentVariable("AZURE_CLIENT_SECRET", secret);

                // create ADT client instance
                DigitalTwinsClient client = new DigitalTwinsClient(new Uri(instanceUrl), new DefaultAzureCredential(new DefaultAzureCredentialOptions {
                    ExcludeVisualStudioCodeCredential = true
                }));

                // read generated DTDL models
                List <string> dtdlModels = new List <string>();
                foreach (string dtdlFilePath in Directory.EnumerateFiles(Path.Combine(Directory.GetCurrentDirectory(), "JSON"), "*.dtdl.json"))
                {
                    dtdlModels.Add(System.IO.File.ReadAllText(dtdlFilePath));
                }

                // upload
                Azure.Response <DigitalTwinsModelData[]> response = client.CreateModelsAsync(dtdlModels).GetAwaiter().GetResult();

                // clear secret
                Environment.SetEnvironmentVariable("AZURE_CLIENT_SECRET", "");

                if (response.GetRawResponse().Status == 201)
                {
                    adtModel.StatusMessage = "Upload successful!";
                    return(View("Index", adtModel));
                }
                else
                {
                    adtModel.StatusMessage = response.GetRawResponse().ReasonPhrase;
                    return(View("Index", adtModel));
                }
            }
            catch (Exception ex)
            {
                // clear secret
                Environment.SetEnvironmentVariable("AZURE_CLIENT_SECRET", "");

                adtModel.StatusMessage = ex.Message;
                return(View("Index", adtModel));
            }
        }
        /// <summary>
        /// Create the ADT block with textures.
        /// </summary>
        public void CreateADTBlock(ADTModel model)
        {
            var adtObject = new GameObject();

            adtObject.name = $"{model.MapId}_{model.X}_{model.Y}";
            adtObject.transform.SetParent(ADTParent.transform);

            var adtMesh = new GameObject();

            adtMesh.name = "mesh";
            adtMesh.transform.SetParent(adtObject.transform);

            var chunkIndex = 0;

            for (var x = 0; x < 256; ++x)
            {
                var mcnkChunk = model.MCNKs[chunkIndex];

                GameObject chunk = GameObject.Instantiate(ChunkPrefab, adtMesh.transform, true);
                chunk.isStatic           = true;
                chunk.name               = $"chunk_{chunkIndex}";
                chunk.transform.position = mcnkChunk.MeshPosition;

                var mesh = new Mesh
                {
                    vertices  = mcnkChunk.VertexArray,
                    triangles = mcnkChunk.TriangleArray,
                    normals   = mcnkChunk.Normals,
                    uv        = mcnkChunk.UVs
                };

                if (model.TexMCNKs[chunkIndex].TextureIds.Length != 0)
                {
                    var fileDataId  = model.TextureFileDataId[(int)model.TexMCNKs[chunkIndex].TextureIds[0]];
                    var textureData = model.TextureDatas[fileDataId];

                    var texture = new Texture2D(textureData.Width, textureData.Height, textureData.TextureFormat, textureData.HasMipmaps);
                    texture.LoadRawTextureData(textureData.RawData);
                    texture.Apply();

                    var material = new Material(Shader.Find("Shader Graphs/WowShader"));
                    material.SetTexture($"_layer0", texture);

                    for (var i = 1; i < model.TexMCNKs[chunkIndex].LayerCount; ++i)
                    {
                        if (model.TexMCNKs[chunkIndex].AlphaLayers == null)
                        {
                            continue;
                        }

                        var alphaWorker = new Texture2D(64, 64, TextureFormat.Alpha8, false);
                        alphaWorker.LoadRawTextureData(model.TexMCNKs[chunkIndex].AlphaLayers[i].Layer);
                        alphaWorker.Apply();
                        alphaWorker.wrapMode = TextureWrapMode.Clamp;

                        fileDataId  = model.TextureFileDataId[(int)model.TexMCNKs[chunkIndex].TextureIds[i]];
                        textureData = model.TextureDatas[fileDataId];

                        texture = new Texture2D(textureData.Width, textureData.Height, textureData.TextureFormat, textureData.HasMipmaps);
                        texture.LoadRawTextureData(textureData.RawData);
                        texture.Apply();

                        material.SetTexture($"_layer{i}", texture);
                        material.SetTexture($"_blend{i}", alphaWorker);
                    }

                    chunk.GetComponent <MeshRenderer>().material = material;
                }

                chunk.GetComponent <MeshFilter>().sharedMesh   = mesh;
                chunk.GetComponent <MeshCollider>().sharedMesh = mesh;
                chunkIndex++;
            }
        }
        public ActionResult GenerateAAS()
        {
            try
            {
                string packagePath = Path.Combine(Directory.GetCurrentDirectory(), "DTDL.aasx");
                using (Package package = Package.Open(packagePath, FileMode.Create))
                {
                    // add package origin part
                    PackagePart origin = package.CreatePart(new Uri("/aasx/aasx-origin", UriKind.Relative), MediaTypeNames.Text.Plain, CompressionOption.Maximum);
                    using (Stream fileStream = origin.GetStream(FileMode.Create))
                    {
                        var bytes = Encoding.ASCII.GetBytes("Intentionally empty.");
                        fileStream.Write(bytes, 0, bytes.Length);
                    }
                    package.CreateRelationship(origin.Uri, TargetMode.Internal, "http://www.admin-shell.io/aasx/relationships/aasx-origin");

                    // create package spec part
                    string packageSpecPath = Path.Combine(Directory.GetCurrentDirectory(), "aasenv-with-no-id.aas.xml");
                    using (StringReader reader = new StringReader(System.IO.File.ReadAllText(packageSpecPath)))
                    {
                        XmlSerializer aasSerializer = new XmlSerializer(typeof(AasEnv));
                        AasEnv        aasEnv        = (AasEnv)aasSerializer.Deserialize(reader);

                        aasEnv.AssetAdministrationShells.AssetAdministrationShell.SubmodelRefs.Clear();
                        aasEnv.Submodels.Clear();

                        foreach (string filenamePath in Directory.EnumerateFiles(Path.Combine(Directory.GetCurrentDirectory(), "JSON"), "*.dtdl.json"))
                        {
                            string submodelPath = Path.Combine(Directory.GetCurrentDirectory(), "submodel.adt.xml");
                            using (StringReader reader2 = new StringReader(System.IO.File.ReadAllText(submodelPath)))
                            {
                                XmlSerializer aasSubModelSerializer = new XmlSerializer(typeof(AASSubModel));
                                AASSubModel   aasSubModel           = (AASSubModel)aasSubModelSerializer.Deserialize(reader2);

                                SubmodelRef nodesetReference = new SubmodelRef();
                                nodesetReference.Keys     = new Keys();
                                nodesetReference.Keys.Key = new Key
                                {
                                    IdType = "URI",
                                    Local  = true,
                                    Type   = "Submodel",
                                    Text   = "http://www.microsoft.com/type/dtdl/" + Path.GetFileNameWithoutExtension(filenamePath).Replace(".", "").ToLower()
                                };

                                aasEnv.AssetAdministrationShells.AssetAdministrationShell.SubmodelRefs.Add(nodesetReference);

                                aasSubModel.Identification.Text += Path.GetFileNameWithoutExtension(filenamePath).Replace(".", "").ToLower();
                                aasSubModel.SubmodelElements.SubmodelElement.SubmodelElementCollection.Value.SubmodelElement.File.Value =
                                    aasSubModel.SubmodelElements.SubmodelElement.SubmodelElementCollection.Value.SubmodelElement.File.Value.Replace("TOBEREPLACED", Path.GetFileNameWithoutExtension(filenamePath));
                                aasEnv.Submodels.Add(aasSubModel);
                            }
                        }

                        XmlTextWriter aasWriter = new XmlTextWriter(packageSpecPath, Encoding.UTF8);
                        aasSerializer.Serialize(aasWriter, aasEnv);
                        aasWriter.Close();
                    }

                    // add package spec part
                    PackagePart spec = package.CreatePart(new Uri("/aasx/aasenv-with-no-id/aasenv-with-no-id.aas.xml", UriKind.Relative), MediaTypeNames.Text.Xml);
                    using (FileStream fileStream = new FileStream(packageSpecPath, FileMode.Open, FileAccess.Read))
                    {
                        CopyStream(fileStream, spec.GetStream());
                    }
                    origin.CreateRelationship(spec.Uri, TargetMode.Internal, "http://www.admin-shell.io/aasx/relationships/aas-spec");

                    // add DTDL files
                    foreach (string filenamePath in Directory.EnumerateFiles(Path.Combine(Directory.GetCurrentDirectory(), "JSON"), "*.dtdl.json"))
                    {
                        PackagePart supplementalDoc = package.CreatePart(new Uri("/aasx/" + Path.GetFileNameWithoutExtension(filenamePath), UriKind.Relative), MediaTypeNames.Application.Json);
                        using (FileStream fileStream = new FileStream(filenamePath, FileMode.Open, FileAccess.Read))
                        {
                            CopyStream(fileStream, supplementalDoc.GetStream());
                        }
                        package.CreateRelationship(supplementalDoc.Uri, TargetMode.Internal, "http://www.admin-shell.io/aasx/relationships/aas-suppl");
                    }
                }

                return(File(new FileStream(Path.Combine(Directory.GetCurrentDirectory(), "DTDL.aasx"), FileMode.Open, FileAccess.Read), "APPLICATION/octet-stream", "DTDL.aasx"));
            }
            catch (Exception ex)
            {
                ADTModel model = new ADTModel
                {
                    StatusMessage = HttpUtility.HtmlDecode(ex.Message)
                };

                return(View("Index", model));
            }
        }