/// <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 }); } }
/// <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 }); } }
/// <summary> /// Appends another vertex data builder /// </summary> /// <param name="other"></param> public void AppendOther(NavigationMeshInputBuilder other) { // Copy vertices int vbase = Points.Count; for (int i = 0; i < other.Points.Count; i++) { Vector3 point = other.Points[i]; Points.Add(point); BoundingBox.Merge(ref BoundingBox, ref point, out BoundingBox); } // Copy indices with offset applied foreach (int index in other.Indices) Indices.Add(index + vbase); }
/// <summary> /// Appends another vertex data builder /// </summary> /// <param name="other"></param> public void AppendOther(NavigationMeshInputBuilder other) { // Copy vertices int vbase = Points.Count; for (int i = 0; i < other.Points.Count; i++) { Vector3 point = other.Points[i]; Points.Add(point); BoundingBox.Merge(ref BoundingBox, ref point, out BoundingBox); } // Copy indices with offset applied foreach (int index in other.Indices) { Indices.Add(index + vbase); } }
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; } }