private void EnsureClonedSceneAndHash()
            {
                if (!sceneCloned)
                {
                    // Hash relevant scene objects
                    if (asset.Scene != null)
                    {
                        string sceneUrl   = AttachedReferenceManager.GetUrl(asset.Scene);
                        var    sceneAsset = (SceneAsset)package.Session.FindAsset(sceneUrl)?.Asset;

                        // Clone scene asset because we update the world transformation matrices
                        clonedSceneAsset = (SceneAsset)AssetCloner.Clone(sceneAsset);

                        // Turn the entire entity hierarchy into a single list
                        sceneEntities = clonedSceneAsset.Hierarchy.Parts.Select(x => x.Entity).ToList();

                        sceneHash = 0;
                        foreach (var entity in sceneEntities)
                        {
                            StaticColliderComponent collider = entity.Get <StaticColliderComponent>();

                            // Only process enabled colliders
                            bool colliderEnabled = collider != null && ((CollisionFilterGroupFlags)collider.CollisionGroup & asset.IncludedCollisionGroups) != 0 && collider.Enabled;
                            if (colliderEnabled) // Removed or disabled
                            {
                                // Update world transform before hashing
                                entity.Transform.UpdateWorldMatrix();

                                // Load collider shape assets since the scene asset is being used, which does not have these loaded by default
                                foreach (var desc in collider.ColliderShapes)
                                {
                                    var shapeAssetDesc = desc as ColliderShapeAssetDesc;
                                    if (shapeAssetDesc?.Shape != null)
                                    {
                                        var assetReference = AttachedReferenceManager.GetAttachedReference(shapeAssetDesc.Shape);
                                        PhysicsColliderShape loadedColliderShape;
                                        if (!loadedColliderShapes.TryGetValue(assetReference.Url, out loadedColliderShape))
                                        {
                                            loadedColliderShape = contentManager.Load <PhysicsColliderShape>(assetReference.Url);
                                            loadedColliderShapes.Add(assetReference.Url, loadedColliderShape); // Store where we loaded the shapes from
                                        }
                                        shapeAssetDesc.Shape = loadedColliderShape;
                                    }
                                }

                                // Finally compute the hash for this collider
                                sceneHash += NavigationMeshBuildUtils.HashEntityCollider(collider);
                            }
                        }
                    }
                    sceneCloned = true;
                }
            }
        /// <summary>
        /// Checks if an entity has moved in any way or it's collider settings/composition changed
        /// </summary>
        /// <param name="newEntity">The entity to check</param>
        /// <returns>true if entity is new, or one of its settings changed affecting the collider's shape</returns>
        public bool IsUpdatedOrNew(Entity newEntity)
        {
            NavigationMeshCachedBuildObject existingObject;
            StaticColliderComponent         collider = newEntity.Get <StaticColliderComponent>();

            if (Objects.TryGetValue(newEntity.Id, out existingObject))
            {
                if (collider != null)
                {
                    int hash = NavigationMeshBuildUtils.HashEntityCollider(collider);
                    return(hash != existingObject.ParameterHash);
                }
            }
            return(true);
        }
        /// <summary>
        /// Registers a new processed object that is build into the navigation mesh
        /// </summary>
        /// <param name="entity">The entity that was processed</param>
        /// <param name="data">The collider vertex data that is generated for this entity</param>
        public void Add(Entity entity, NavigationMeshInputBuilder data)
        {
            StaticColliderComponent collider = entity.Get <StaticColliderComponent>();

            if (collider != null)
            {
                int hash = NavigationMeshBuildUtils.HashEntityCollider(collider);
                Objects.Add(entity.Id, new NavigationMeshCachedBuildObject()
                {
                    Guid          = entity.Id,
                    ParameterHash = hash,
                    Data          = data
                });
            }
        }
            private void CollectInputGeometry()
            {
                // Reset state
                fullRebuild = false;
                updatedAreas.Clear();
                fullRebuild = oldBuild == null;

                sceneNavigationMeshInputBuilder = new NavigationMeshInputBuilder();

                foreach (var entity in sceneEntities)
                {
                    StaticColliderComponent collider = entity.Get <StaticColliderComponent>();

                    bool colliderEnabled = collider != null && ((CollisionFilterGroupFlags)collider.CollisionGroup & asset.IncludedCollisionGroups) != 0 && collider.Enabled;
                    if (colliderEnabled)
                    {
                        // Compose collider shape, mark as disabled if it failed so the respective area gets updated
                        collider.ComposeShape();
                        if (collider.ColliderShape == null)
                        {
                            colliderEnabled = false;
                        }
                    }

                    if (!colliderEnabled) // Removed or disabled
                    {
                        // Check for old object
                        NavigationMeshCachedBuildObject oldObject = null;
                        if (oldBuild?.Objects.TryGetValue(entity.Id, out oldObject) ?? false)
                        {
                            // This object has been disabled, update the area because it was removed and continue to the next object
                            updatedAreas.Add(oldObject.Data.BoundingBox);
                        }
                    }
                    else if (oldBuild?.IsUpdatedOrNew(entity) ?? true) // Is the entity updated?
                    {
                        TransformComponent entityTransform   = entity.Transform;
                        Matrix             entityWorldMatrix = entityTransform.WorldMatrix;

                        NavigationMeshInputBuilder entityNavigationMeshInputBuilder = new NavigationMeshInputBuilder();

                        bool isDeferred = false;

                        // Interate through all the colliders shapes while queueing all shapes in compound shapes to process those as well
                        Queue <ColliderShape> shapesToProcess = new Queue <ColliderShape>();
                        shapesToProcess.Enqueue(collider.ColliderShape);
                        while (shapesToProcess.Count > 0)
                        {
                            var shape     = shapesToProcess.Dequeue();
                            var shapeType = shape.GetType();
                            if (shapeType == typeof(BoxColliderShape))
                            {
                                var    box       = (BoxColliderShape)shape;
                                var    boxDesc   = (BoxColliderShapeDesc)box.Description;
                                Matrix transform = box.PositiveCenterMatrix * entityWorldMatrix;

                                var meshData = GeometricPrimitive.Cube.New(boxDesc.Size);
                                entityNavigationMeshInputBuilder.AppendMeshData(meshData, transform);
                            }
                            else if (shapeType == typeof(SphereColliderShape))
                            {
                                var    sphere     = (SphereColliderShape)shape;
                                var    sphereDesc = (SphereColliderShapeDesc)sphere.Description;
                                Matrix transform  = sphere.PositiveCenterMatrix * entityWorldMatrix;

                                var meshData = GeometricPrimitive.Sphere.New(sphereDesc.Radius);
                                entityNavigationMeshInputBuilder.AppendMeshData(meshData, transform);
                            }
                            else if (shapeType == typeof(CylinderColliderShape))
                            {
                                var    cylinder     = (CylinderColliderShape)shape;
                                var    cylinderDesc = (CylinderColliderShapeDesc)cylinder.Description;
                                Matrix transform    = cylinder.PositiveCenterMatrix * entityWorldMatrix;

                                var meshData = GeometricPrimitive.Cylinder.New(cylinderDesc.Height, cylinderDesc.Radius);
                                entityNavigationMeshInputBuilder.AppendMeshData(meshData, transform);
                            }
                            else if (shapeType == typeof(CapsuleColliderShape))
                            {
                                var    capsule     = (CapsuleColliderShape)shape;
                                var    capsuleDesc = (CapsuleColliderShapeDesc)capsule.Description;
                                Matrix transform   = capsule.PositiveCenterMatrix * entityWorldMatrix;

                                var meshData = GeometricPrimitive.Capsule.New(capsuleDesc.Length, capsuleDesc.Radius);
                                entityNavigationMeshInputBuilder.AppendMeshData(meshData, transform);
                            }
                            else if (shapeType == typeof(ConeColliderShape))
                            {
                                var    cone      = (ConeColliderShape)shape;
                                var    coneDesc  = (ConeColliderShapeDesc)cone.Description;
                                Matrix transform = cone.PositiveCenterMatrix * entityWorldMatrix;

                                var meshData = GeometricPrimitive.Cone.New(coneDesc.Radius, coneDesc.Height);
                                entityNavigationMeshInputBuilder.AppendMeshData(meshData, transform);
                            }
                            else if (shapeType == typeof(StaticPlaneColliderShape))
                            {
                                var    plane     = (StaticPlaneColliderShape)shape;
                                var    planeDesc = (StaticPlaneColliderShapeDesc)plane.Description;
                                Matrix transform = plane.PositiveCenterMatrix * entityWorldMatrix;

                                // Defer infinite planes because their size is not defined yet
                                deferredShapes.Add(new DeferredShape
                                {
                                    Description = planeDesc,
                                    Transform   = transform,
                                    Entity      = entity,
                                    NavigationMeshInputBuilder = entityNavigationMeshInputBuilder
                                });
                                isDeferred = true;
                            }
                            else if (shapeType == typeof(ConvexHullColliderShape))
                            {
                                var    hull      = (ConvexHullColliderShape)shape;
                                Matrix transform = hull.PositiveCenterMatrix * entityWorldMatrix;

                                // Convert hull indices to int
                                int[] indices = new int[hull.Indices.Count];
                                if (hull.Indices.Count % 3 != 0)
                                {
                                    throw new InvalidOperationException("Physics hull does not consist of triangles");
                                }
                                for (int i = 0; i < hull.Indices.Count; i += 3)
                                {
                                    indices[i]     = (int)hull.Indices[i];
                                    indices[i + 1] = (int)hull.Indices[i + 1];
                                    indices[i + 2] = (int)hull.Indices[i + 2];
                                }

                                entityNavigationMeshInputBuilder.AppendArrays(hull.Points.ToArray(), indices, transform);
                            }
                            else if (shapeType == typeof(CompoundColliderShape))
                            {
                                // Unroll compound collider shapes
                                var compound = (CompoundColliderShape)shape;
                                for (int i = 0; i < compound.Count; i++)
                                {
                                    shapesToProcess.Enqueue(compound[i]);
                                }
                            }
                        }

                        if (!isDeferred)
                        {
                            // Store current entity in build cache
                            currentBuild.Add(entity, entityNavigationMeshInputBuilder);

                            // Add (?old) and new bounding box to modified areas
                            sceneNavigationMeshInputBuilder.AppendOther(entityNavigationMeshInputBuilder);
                            NavigationMeshCachedBuildObject oldObject = null;
                            if (oldBuild?.Objects.TryGetValue(entity.Id, out oldObject) ?? false)
                            {
                                updatedAreas.Add(oldObject.Data.BoundingBox);
                            }
                            updatedAreas.Add(entityNavigationMeshInputBuilder.BoundingBox);
                        }
                    }
                    else // Not updated
                    {
                        // Copy old data into vertex buffer
                        NavigationMeshCachedBuildObject oldObject = oldBuild.Objects[entity.Id];
                        sceneNavigationMeshInputBuilder.AppendOther(oldObject.Data);
                        currentBuild.Add(entity, oldObject.Data);
                    }
                }

                // Unload loaded collider shapes
                foreach (var pair in loadedColliderShapes)
                {
                    contentManager.Unload(pair.Key);
                }

                // Store calculated bounding box
                if (generateBoundingBox)
                {
                    globalBoundingBox = sceneNavigationMeshInputBuilder.BoundingBox;
                }

                // Process deferred shapes
                Vector3 maxSize     = globalBoundingBox.Maximum - globalBoundingBox.Minimum;
                float   maxDiagonal = Math.Max(maxSize.X, Math.Max(maxSize.Y, maxSize.Z));

                foreach (DeferredShape shape in deferredShapes)
                {
                    StaticPlaneColliderShapeDesc planeDesc = (StaticPlaneColliderShapeDesc)shape.Description;
                    Plane plane = new Plane(planeDesc.Normal, planeDesc.Offset);

                    // Pre-Transform plane parameters
                    plane.Normal = Vector3.TransformNormal(plane.Normal, shape.Transform);
                    float offset = Vector3.Dot(shape.Transform.TranslationVector, plane.Normal);
                    plane.D += offset;

                    // Generate source plane triangles
                    Vector3[] planePoints;
                    int[]     planeInds;
                    NavigationMeshBuildUtils.BuildPlanePoints(ref plane, maxDiagonal, out planePoints, out planeInds);

                    Vector3 tangent, bitangent;
                    NavigationMeshBuildUtils.GenerateTangentBinormal(plane.Normal, out tangent, out bitangent);
                    // Calculate plane offset so that the plane always covers the whole range of the bounding box
                    Vector3 planeOffset = Vector3.Dot(globalBoundingBox.Center, tangent) * tangent;
                    planeOffset += Vector3.Dot(globalBoundingBox.Center, bitangent) * bitangent;

                    VertexPositionNormalTexture[] vertices = new VertexPositionNormalTexture[planePoints.Length];
                    for (int i = 0; i < planePoints.Length; i++)
                    {
                        vertices[i] = new VertexPositionNormalTexture(planePoints[i] + planeOffset, Vector3.UnitY, Vector2.Zero);
                    }

                    GeometricMeshData <VertexPositionNormalTexture> meshData = new GeometricMeshData <VertexPositionNormalTexture>(vertices, planeInds, false);
                    shape.NavigationMeshInputBuilder.AppendMeshData(meshData, Matrix.Identity);
                    sceneNavigationMeshInputBuilder.AppendMeshData(meshData, Matrix.Identity);
                    // Update bounding box after plane generation
                    if (generateBoundingBox)
                    {
                        globalBoundingBox = sceneNavigationMeshInputBuilder.BoundingBox;
                    }

                    // Store deferred shape in build cahce just like normal onesdddd
                    currentBuild.Add(shape.Entity, shape.NavigationMeshInputBuilder);

                    // NOTE: Force a full rebuild when moving unbound shapes such as ininite planes
                    // the alternative is to intersect the old and new plane with the tiles to see which ones changed
                    // although in most cases it will be a horizontal plane and all tiles will be affected anyways
                    fullRebuild = true;
                }
            }
            protected override Task <ResultStatus> DoCommandOverride(ICommandContext commandContext)
            {
                var intermediateDataId = ComputeAssetIntermediateDataId();

                // Build cache items to build incrementally
                currentBuild = new NavigationMeshCachedBuild();
                oldBuild     = LoadIntermediateData(intermediateDataId);

                // The output object of the compilation
                NavigationMesh generatedNavigationMesh = new NavigationMesh();

                // No scene specified, result in failure
                if (asset.Scene == null)
                {
                    return(Task.FromResult(ResultStatus.Failed));
                }

                if (asset.AutoGenerateBoundingBox)
                {
                    generateBoundingBox = true;
                    globalBoundingBox   = BoundingBox.Empty;
                }
                else
                {
                    generateBoundingBox = false;
                    globalBoundingBox   = asset.BoundingBox;
                }

                // Copy build settings so we can modify them
                buildSettings = asset.BuildSettings;

                // Check for tile size
                if (buildSettings.TileSize <= 0)
                {
                    return(Task.FromResult(ResultStatus.Failed));
                }

                // Clone scene, obtain hash and load collider shape assets
                EnsureClonedSceneAndHash();
                if (clonedSceneAsset == null)
                {
                    return(Task.FromResult(ResultStatus.Failed));
                }

                // This collects all the input geometry, calculates the modified areas and calculates the scene bounds
                CollectInputGeometry();
                List <BoundingBox> removedAreas = oldBuild?.GetRemovedAreas(sceneEntities);

                BoundingBox boundingBox = globalBoundingBox;

                // Can't generate when no bounding box or and invalid bounding box is specified
                // this means that either the user specified bounding box is wrong or the scene does not contain any colliders
                if (boundingBox.Extent.X > 0 && boundingBox.Extent.Y > 0 && boundingBox.Extent.Z > 0)
                {
                    // Turn generated data into arrays
                    Vector3[] meshVertices = sceneNavigationMeshInputBuilder.Points.ToArray();
                    int[]     meshIndices  = sceneNavigationMeshInputBuilder.Indices.ToArray();

                    // NOTE: Reversed winding order as input to recast
                    int numSrcTriangles = meshIndices.Length / 3;
                    for (int i = 0; i < numSrcTriangles; i++)
                    {
                        int j = meshIndices[i * 3 + 1];
                        meshIndices[i * 3 + 1] = meshIndices[i * 3 + 2];
                        meshIndices[i * 3 + 2] = j;
                    }

                    // Check if settings changed to trigger a full rebuild
                    int currentSettingsHash = asset.GetHashCode();
                    currentBuild.SettingsHash = currentSettingsHash;
                    if (oldBuild != null && oldBuild.SettingsHash != currentBuild.SettingsHash)
                    {
                        fullRebuild = true;
                    }

                    if (oldBuild != null && !fullRebuild)
                    {
                        // Perform incremental build on old navigation mesh
                        generatedNavigationMesh = oldBuild.NavigationMesh;
                    }

                    // Initialize navigation mesh for building
                    generatedNavigationMesh.Initialize(buildSettings, asset.NavigationMeshAgentSettings.ToArray());

                    // Set the new navigation mesh in the current build
                    currentBuild.NavigationMesh = generatedNavigationMesh;

                    // Generate all the layers corresponding to the various agent settings
                    for (int layer = 0; layer < asset.NavigationMeshAgentSettings.Count; layer++)
                    {
                        Stopwatch layerBuildTimer = new Stopwatch();
                        layerBuildTimer.Start();
                        var agentSetting = asset.NavigationMeshAgentSettings[layer];

                        // Flag tiles to build for this specific layer
                        HashSet <Point> tilesToBuild = new HashSet <Point>();
                        if (fullRebuild)
                        {
                            // For full rebuild just take the root bounding box for selecting tiles to build
                            List <Point> newTileList = NavigationMeshBuildUtils.GetOverlappingTiles(buildSettings, boundingBox);
                            foreach (Point p in newTileList)
                            {
                                tilesToBuild.Add(p);
                            }
                        }
                        else
                        {
                            // Apply an offset so their neighbouring tiles which are affected by the agent radius also get rebuild
                            Vector3 agentOffset = new Vector3(agentSetting.Radius, 0, agentSetting.Radius);
                            if (removedAreas != null)
                            {
                                updatedAreas.AddRange(removedAreas);
                            }
                            foreach (var update in updatedAreas)
                            {
                                BoundingBox agentSpecificBoundingBox = new BoundingBox
                                {
                                    Minimum = update.Minimum - agentOffset,
                                    Maximum = update.Maximum + agentOffset,
                                };
                                List <Point> newTileList = NavigationMeshBuildUtils.GetOverlappingTiles(buildSettings, agentSpecificBoundingBox);
                                foreach (Point p in newTileList)
                                {
                                    tilesToBuild.Add(p);
                                }
                            }
                        }

                        // Build tiles
                        foreach (var tileToBuild in tilesToBuild)
                        {
                            BoundingBox tileBoundingBox = NavigationMeshBuildUtils.ClampBoundingBoxToTile(buildSettings, boundingBox, tileToBuild);
                            // Check if tile bounding box is contained withing the navigation mesh bounding box
                            if (boundingBox.Contains(ref tileBoundingBox) == ContainmentType.Disjoint)
                            {
                                // Remove this tile
                                generatedNavigationMesh.Layers[layer].RemoveLayerTile(tileToBuild);
                                continue;
                            }

                            // Build the tile for the current layer being processed
                            generatedNavigationMesh.Layers[layer].BuildTile(meshVertices.ToArray(), meshIndices.ToArray(), tileBoundingBox, tileToBuild);
                        }
                    }
                }

                // Store used bounding box in navigation mesh
                generatedNavigationMesh.BoundingBox = boundingBox;

                contentManager.Save(assetUrl, generatedNavigationMesh);
                SaveIntermediateData(intermediateDataId, currentBuild);

                return(Task.FromResult(ResultStatus.Successful));
            }