/// <summary> /// Sets the spatial partition. (For use by the content pipeline only.) /// </summary> /// <param name="partition">The spatial partition.</param> /// <remarks> /// This method is used internally to directly set the spatial partition. The spatial partition /// might already be initialized and should not be invalidated. /// </remarks> internal void SetPartition(ISpatialPartition <int> partition) { if (partition != null) { _partition = partition; _partition.GetAabbForItem = i => Children[i].Aabb; // ----- Validate spatial partition. // Some spatial partitions, such as the CompressedAabbTree, are pre-initialized when // loaded via content pipeline. Other spatial partitions need to be initialized manually. int numberOfChildren = Children.Count; if (_partition.Count != numberOfChildren) { // The partition is not initialized. _partition.Clear(); for (int i = 0; i < numberOfChildren; i++) { _partition.Add(i); } _partition.Update(false); } else { // The partition is already initialized. Debug.Assert(Enumerable.Range(0, numberOfChildren).All(_partition.Contains), "Invalid partition. The pre-initialized partition does not contain the same children as the CompositeShape."); } } }
/// <summary> /// Called when the <see cref="Children"/> collection was changed. /// </summary> /// <param name="sender">The sender.</param> /// <param name="eventArgs"> /// The <see cref="CollectionChangedEventArgs{IGeometricObject}"/> instance containing the event /// data. /// </param> private void OnChildrenChanged(object sender, CollectionChangedEventArgs <IGeometricObject> eventArgs) { if (eventArgs.Action == CollectionChangedAction.Move) { return; } // Handle removed items. var oldItems = eventArgs.OldItems; int numberOfOldItems = oldItems.Count; for (int i = 0; i < numberOfOldItems; i++) { var geometricObject = oldItems[i]; geometricObject.PoseChanged -= OnChildShapeChanged; geometricObject.ShapeChanged -= OnChildShapeChanged; } // Handle new items. var newItems = eventArgs.NewItems; int numberOfNewItems = newItems.Count; for (int i = 0; i < numberOfNewItems; i++) { var geometricObject = newItems[i]; geometricObject.PoseChanged += OnChildShapeChanged; geometricObject.ShapeChanged += OnChildShapeChanged; } // Rebuild spatial partition. if (_partition != null) { _partition.Clear(); int numberOfChildren = Children.Count; for (int i = 0; i < numberOfChildren; i++) { _partition.Add(i); } } if (numberOfOldItems == 1 && numberOfNewItems == 1 && eventArgs.OldItemsIndex == eventArgs.NewItemsIndex) { // Exactly one item was replaced. // --> Set the feature index of the item in the event args. var shapeChangedEventArgs = ShapeChangedEventArgs.Create(eventArgs.OldItemsIndex); OnChanged(shapeChangedEventArgs); shapeChangedEventArgs.Recycle(); } else { // Multiple items added or removed. The indices of multiple items have changed. // --> Do not set a feature index. Use the default. OnChanged(ShapeChangedEventArgs.Empty); } }
public SpatialPartitionSample(Microsoft.Xna.Framework.Game game) : base(game) { SampleFramework.IsMouseVisible = false; GraphicsScreen.ClearBackground = true; GraphicsScreen.BackgroundColor = Color.Gray; GraphicsScreen.DrawReticle = true; SetCamera(new Vector3(0, 1, 10), 0, 0); // Create a spatial partition. DigitalRune Geometry supports several types, see also // http://digitalrune.github.io/DigitalRune-Documentation/html/e32cab3b-cc7c-42ee-8ec9-23dd4467edd0.htm#WhichPartition // An AabbTree is useful for static objects. A DynamicAabbTree is good for moving objects. // The spatial partition can manage different types of items. In this case it manages // GeometricObjects. A delegate has to inform the spatial partition how to get the AABB // of an object. //_spatialPartition = new DynamicAabbTree<GeometricObject> _spatialPartition = new AabbTree <GeometricObject> { GetAabbForItem = geometricObject => geometricObject.Aabb, // Optional: The tree is automatically built using a mixed top-down/bottom-up approach. // Bottom-up building is slower but produces better trees. If the tree building takes too // long, we can lower the BottomUpBuildThreshold (default is 128). //BottomUpBuildThreshold = 0, // Optional: A filter can be set to disable certain kind of overlaps. //Filter = ... }; // Create a triangle mesh. var triangleMesh = new SphereShape(1).GetMesh(0.01f, 4); var triangleMeshShape = new TriangleMeshShape(triangleMesh) { // TriangleMeshShapes can also use a spatial partition to manage triangle. // The items in the spatial partition are the triangle indices. The GetAabbForItem // delegate is set automatically. Partition = new AabbTree <int>(), }; // Spatial partitions are built automatically when needed. However, it is still recommended // to call Update to initialize the spatial partition explicitly. triangleMeshShape.Partition.Update(false); // Add a lot of triangle mesh objects to _spatialPartition. var random = new Random(); for (int i = 0; i < 50; i++) { var randomPosition = new Vector3(random.NextFloat(-6, 6), random.NextFloat(-3, 3), random.NextFloat(-10, 0)); var geometricObject = new GeometricObject(triangleMeshShape, new Pose(randomPosition)); _spatialPartition.Add(geometricObject); } _spatialPartition.Update(false); }
/// <summary> /// Invalidates the whole triangle mesh or a single triangle. /// </summary> /// <param name="triangleIndex"> /// Index of the triangle. Can be -1 to invalidate the whole mesh. /// </param> /// <param name="invalidateTopology"> /// If set to <see langword="true"/> the mesh topology is invalidated. /// </param> /// <remarks> /// <para> /// This method must be called if the position of a triangle stored in <see cref="Mesh"/> is /// changed. This method updates the <see cref="Partition"/> and raises the /// <see cref="Shape.Changed"/> event by calling <see cref="Shape.OnChanged"/>. /// </para> /// <para> /// If the mesh topology has changed, <paramref name="invalidateTopology"/> must be set to /// <see langword="true"/>. The topology has changed if triangle neighbor relationships have /// changed. If each triangle has the same neighbor triangles as before and only the vertices /// were moved, <paramref name="invalidateTopology"/> can be <see langword="false"/>. /// </para> /// </remarks> /// <exception cref="ArgumentOutOfRangeException"> /// <paramref name="triangleIndex"/> is out of range. /// </exception> public void Invalidate(int triangleIndex, bool invalidateTopology) { int numberOfTriangles = Mesh.NumberOfTriangles; if (triangleIndex >= numberOfTriangles) { throw new ArgumentOutOfRangeException("triangleIndex"); } // Set cached AABB to "invalid". _aabbLocal = new Aabb(new Vector3(float.NaN), new Vector3(float.NaN)); // Fill new spatial partition. if (_partition != null) { if (numberOfTriangles != _partition.Count) { // Triangle count has changed. Re-initialize partition content. _partition.Clear(); for (int i = 0; i < numberOfTriangles; i++) { _partition.Add(i); } } else { // Same number of triangles - invalidate the triangle. if (triangleIndex >= 0) { _partition.Invalidate(triangleIndex); } else { _partition.Invalidate(); } } } if (invalidateTopology) { ComputeTriangleNeighbors(); } if (triangleIndex < 0) { OnChanged(ShapeChangedEventArgs.Empty); } else { var eventArgs = ShapeChangedEventArgs.Create(triangleIndex); OnChanged(eventArgs); eventArgs.Recycle(); } }
public SpatialPartitionSample(Microsoft.Xna.Framework.Game game) : base(game) { SampleFramework.IsMouseVisible = false; GraphicsScreen.ClearBackground = true; GraphicsScreen.BackgroundColor = Color.Gray; GraphicsScreen.DrawReticle = true; SetCamera(new Vector3F(0, 1, 10), 0, 0); // Create a spatial partition. DigitalRune Geometry supports several types, see also // http://digitalrune.github.io/DigitalRune-Documentation/html/e32cab3b-cc7c-42ee-8ec9-23dd4467edd0.htm#WhichPartition // An AabbTree is useful for static objects. A DynamicAabbTree is good for moving objects. // The spatial partition can manage different types of items. In this case it manages // GeometricObjects. A delegate has to inform the spatial partition how to get the AABB // of an object. //_spatialPartition = new DynamicAabbTree<GeometricObject> _spatialPartition = new AabbTree<GeometricObject> { GetAabbForItem = geometricObject => geometricObject.Aabb, // Optional: The tree is automatically built using a mixed top-down/bottom-up approach. // Bottom-up building is slower but produces better trees. If the tree building takes too // long, we can lower the BottomUpBuildThreshold (default is 128). //BottomUpBuildThreshold = 0, // Optional: A filter can be set to disable certain kind of overlaps. //Filter = ... }; // Create a triangle mesh. var triangleMesh = new SphereShape(1).GetMesh(0.01f, 4); var triangleMeshShape = new TriangleMeshShape(triangleMesh) { // TriangleMeshShapes can also use a spatial partition to manage triangle. // The items in the spatial partition are the triangle indices. The GetAabbForItem // delegate is set automatically. Partition = new AabbTree<int>(), }; // Spatial partitions are built automatically when needed. However, it is still recommended // to call Update to initialize the spatial partition explicitly. triangleMeshShape.Partition.Update(false); // Add a lot of triangle mesh objects to _spatialPartition. var random = new Random(); for (int i = 0; i < 50; i++) { var randomPosition = new Vector3F(random.NextFloat(-6, 6), random.NextFloat(-3, 3), random.NextFloat(-10, 0)); var geometricObject = new GeometricObject(triangleMeshShape, new Pose(randomPosition)); _spatialPartition.Add(geometricObject); } _spatialPartition.Update(false); }
public void SetUp() { RandomHelper.Random = new Random(1234567); _testObjects.Clear(); _testObjectsOfPartition2.Clear(); TestObject.NextId = 0; _testObjectsOfPartition2.Add(new TestObject(GetRandomAabb())); _testObjectsOfPartition2.Add(new TestObject(GetRandomAabb())); _testObjectsOfPartition2.Add(new TestObject(GetRandomAabb())); _testObjectsOfPartition2.Add(new TestObject(GetRandomAabb())); _partition2 = new AabbTree <int>(); _partition2.GetAabbForItem = GetAabbOfTestObjectOfPartition2; _partition2.Add(0); _partition2.Add(1); _partition2.Add(2); _partition2.Add(3); _conservativeAabb = false; _conservativeOverlaps = false; }
public void SetUp() { RandomHelper.Random = new Random(1234567); _testObjects.Clear(); _testObjectsOfPartition2.Clear(); TestObject.NextId = 0; _testObjectsOfPartition2.Add(new TestObject(GetRandomAabb())); _testObjectsOfPartition2.Add(new TestObject(GetRandomAabb())); _testObjectsOfPartition2.Add(new TestObject(GetRandomAabb())); _testObjectsOfPartition2.Add(new TestObject(GetRandomAabb())); _partition2 = new AabbTree<int>(); _partition2.GetAabbForItem = GetAabbOfTestObjectOfPartition2; _partition2.Add(0); _partition2.Add(1); _partition2.Add(2); _partition2.Add(3); _conservativeAabb = false; _conservativeOverlaps = false; }
private void TestPartition(ISpatialPartition <int> partition) { partition.Clear(); Assert.AreEqual(0, partition.Count); partition.EnableSelfOverlaps = true; Assert.AreEqual(0, partition.GetOverlaps().Count()); Assert.AreEqual(0, partition.GetOverlaps(0).Count()); Assert.AreEqual(0, partition.GetOverlaps(new Aabb()).Count()); Assert.AreEqual(0, partition.GetOverlaps(_partition2).Count()); Assert.AreEqual(0, partition.GetOverlaps(Vector3.One, Pose.Identity, _partition2, Vector3.One, Pose.Identity).Count()); var testObject = new TestObject(new Aabb(new Vector3(10), new Vector3(10))); _testObjects.Add(testObject); partition.Add(testObject.Id); for (int i = 0; i < 1000; i++) { // ----- Tests Assert.AreEqual(_testObjects.Count, partition.Count, "Wrong number of items."); if (i > 10 && i % 6 == 0) { TestGetOverlaps0(partition); } if (i > 10 && i % 6 == 1) { TestGetOverlaps1(partition); } if (i > 10 && i % 6 == 2) { TestGetOverlaps2(partition); } if (i > 10 && i % 6 == 3) { TestGetOverlaps3(partition); } if (i > 10 && i % 6 == 4) { TestGetOverlaps4(partition); } if (i > 10 && i % 6 == 5) { TestGetOverlaps5(partition); } // Update partition. From time to time rebuild all. // For the above tests update should have been called automatically! partition.Update(i % 10 == 9); TestAabb(partition); var dice100 = RandomHelper.Random.Next(0, 100); if (dice100 < 2) { // Test remove/re-add without Update inbetween. if (partition.Count > 0) { partition.Remove(_testObjects[0].Id); partition.Add(_testObjects[0].Id); } } dice100 = RandomHelper.Random.Next(0, 100); if (dice100 < 10) { // Remove objects. int removeCount = RandomHelper.Random.NextInteger(1, 4); for (int k = 0; k < removeCount && partition.Count > 0; k++) { var index = RandomHelper.Random.NextInteger(0, partition.Count - 1); var obj = _testObjects[index]; _testObjects.Remove(obj); partition.Remove(obj.Id); } } dice100 = RandomHelper.Random.Next(0, 100); if (dice100 < 10) { // Add new objects. int addCount = RandomHelper.Random.NextInteger(1, 4); for (int k = 0; k < addCount; k++) { var newObj = new TestObject(GetRandomAabb()); _testObjects.Add(newObj); partition.Add(newObj.Id); } } else { // Move an object. int moveCount = RandomHelper.Random.NextInteger(1, 10); for (int k = 0; k < moveCount && partition.Count > 0; k++) { var index = RandomHelper.Random.NextInteger(0, partition.Count - 1); var obj = _testObjects[index]; obj.Aabb = GetRandomAabb(); partition.Invalidate(obj.Id); } } // From time to time invalidate all. if (dice100 < 3) { partition.Invalidate(); } // From time to time change EnableSelfOverlaps. if (dice100 > 3 && dice100 < 6) { partition.EnableSelfOverlaps = false; } else if (dice100 < 10) { partition.EnableSelfOverlaps = true; } // From time to time change filter. if (dice100 > 10 && dice100 < 13) { partition.Filter = null; } else if (dice100 < 10) { if (partition.Filter == null) { partition.Filter = new DelegatePairFilter <int>(AreInSameGroup); } } } partition.Clear(); Assert.AreEqual(0, partition.Count); }
//-------------------------------------------------------------- #region Methods //-------------------------------------------------------------- #if XNA || MONOGAME /// <summary> /// Sets the spatial partition. (For use by the content pipeline only.) /// </summary> /// <param name="partition">The spatial partition.</param> /// <remarks> /// This method is used internally to directly set the spatial partition. The spatial partition /// might already be initialized and should not be invalidated. /// </remarks> internal void SetPartition(ISpatialPartition<int> partition) { if (partition != null) { _partition = partition; _partition.GetAabbForItem = i => Children[i].Aabb; // ----- Validate spatial partition. // Some spatial partitions, such as the CompressedAabbTree, are pre-initialized when // loaded via content pipeline. Other spatial partitions need to be initialized manually. int numberOfChildren = Children.Count; if (_partition.Count != numberOfChildren) { // The partition is not initialized. _partition.Clear(); for (int i = 0; i < numberOfChildren; i++) _partition.Add(i); _partition.Update(false); } else { // The partition is already initialized. Debug.Assert(Enumerable.Range(0, numberOfChildren).All(_partition.Contains), "Invalid partition. The pre-initialized partition does not contain the same children as the CompositeShape."); } } }
private void TestPartition(ISpatialPartition<int> partition) { partition.Clear(); Assert.AreEqual(0, partition.Count); partition.EnableSelfOverlaps = true; Assert.AreEqual(0, partition.GetOverlaps().Count()); Assert.AreEqual(0, partition.GetOverlaps(0).Count()); Assert.AreEqual(0, partition.GetOverlaps(new Aabb()).Count()); Assert.AreEqual(0, partition.GetOverlaps(_partition2).Count()); Assert.AreEqual(0, partition.GetOverlaps(Vector3F.One, Pose.Identity, _partition2, Vector3F.One, Pose.Identity).Count()); var testObject = new TestObject(new Aabb(new Vector3F(10), new Vector3F(10))); _testObjects.Add(testObject); partition.Add(testObject.Id); for (int i = 0; i < 1000; i++) { // ----- Tests Assert.AreEqual(_testObjects.Count, partition.Count, "Wrong number of items."); if (i > 10 && i % 6 == 0) TestGetOverlaps0(partition); if (i > 10 && i % 6 == 1) TestGetOverlaps1(partition); if (i > 10 && i % 6 == 2) TestGetOverlaps2(partition); if (i > 10 && i % 6 == 3) TestGetOverlaps3(partition); if (i > 10 && i % 6 == 4) TestGetOverlaps4(partition); if (i > 10 && i % 6 == 5) TestGetOverlaps5(partition); // Update partition. From time to time rebuild all. // For the above tests update should have been called automatically! partition.Update(i % 10 == 9); TestAabb(partition); var dice100 = RandomHelper.Random.Next(0, 100); if (dice100 < 2) { // Test remove/re-add without Update inbetween. if (partition.Count > 0) { partition.Remove(_testObjects[0].Id); partition.Add(_testObjects[0].Id); } } dice100 = RandomHelper.Random.Next(0, 100); if (dice100 < 10) { // Remove objects. int removeCount = RandomHelper.Random.NextInteger(1, 4); for (int k = 0; k < removeCount && partition.Count > 0; k++) { var index = RandomHelper.Random.NextInteger(0, partition.Count - 1); var obj = _testObjects[index]; _testObjects.Remove(obj); partition.Remove(obj.Id); } } dice100 = RandomHelper.Random.Next(0, 100); if (dice100 < 10) { // Add new objects. int addCount = RandomHelper.Random.NextInteger(1, 4); for (int k = 0; k < addCount; k++) { var newObj = new TestObject(GetRandomAabb()); _testObjects.Add(newObj); partition.Add(newObj.Id); } } else { // Move an object. int moveCount = RandomHelper.Random.NextInteger(1, 10); for (int k = 0; k < moveCount && partition.Count > 0; k++) { var index = RandomHelper.Random.NextInteger(0, partition.Count - 1); var obj = _testObjects[index]; obj.Aabb = GetRandomAabb(); partition.Invalidate(obj.Id); } } // From time to time invalidate all. if (dice100 < 3) partition.Invalidate(); // From time to time change EnableSelfOverlaps. if (dice100 > 3 && dice100 < 6) partition.EnableSelfOverlaps = false; else if (dice100 < 10) partition.EnableSelfOverlaps = true; // From time to time change filter. if (dice100 > 10 && dice100 < 13) { partition.Filter = null; } else if (dice100 < 10) { if (partition.Filter == null) partition.Filter = new DelegatePairFilter<int>(AreInSameGroup); } } partition.Clear(); Assert.AreEqual(0, partition.Count); }