protected virtual void Remove(int contactIndex) { Contact removing = contacts.Elements[contactIndex]; contacts.FastRemoveAt(contactIndex); OnRemoved(removing); unusedContacts.GiveBack(removing); }
private static void MaintainEdge(int a, int b, RawList <int> edges) { bool contained = false; int index = 0; for (int k = 0; k < edges.Count; k += 2) { if ((edges[k] == a && edges[k + 1] == b) || (edges[k] == b && edges[k + 1] == a)) { contained = true; index = k; } } //If it isn't present, add it to the edge list. if (!contained) { edges.Add(a); edges.Add(b); } else { //If it is present, that means both edge-connected triangles were deleted now, so get rid of it. edges.FastRemoveAt(index + 1); edges.FastRemoveAt(index); } }
/// <summary> /// Removes redundant points. Two points are redundant if they occupy the same hash grid cell. /// </summary> /// <param name="points">List of points to prune.</param> /// <param name="cellSize">Size of cells to determine redundancy.</param> public static void RemoveRedundantPoints(RawList <Vector3> points, double cellSize) { var set = BlockedCellSets.Take(); for (int i = points.Count - 1; i >= 0; --i) { var element = points.Elements[i]; var cell = new BlockedCell { X = (int)Math.Floor(element.X / cellSize), Y = (int)Math.Floor(element.Y / cellSize), Z = (int)Math.Floor(element.Z / cellSize) }; if (set.Contains(cell)) { points.FastRemoveAt(i); } else { set.Add(cell); //TODO: Consider adding adjacent cells to guarantee that a point on the border between two cells will still detect the presence //of a point on the opposite side of that border. } } set.Clear(); BlockedCellSets.GiveBack(set); }
void DeactivateObjects() { //Deactivate only some objects each frame. int numberOfEntitiesDeactivated = 0; int numberOfIslandsChecked = 0; int originalIslandCount = simulationIslands.Count; while (numberOfEntitiesDeactivated < maximumDeactivationAttemptsPerFrame && simulationIslands.Count > 0 && numberOfIslandsChecked < originalIslandCount) { deactivationIslandIndex = (deactivationIslandIndex + 1) % simulationIslands.Count; var island = simulationIslands.Elements[deactivationIslandIndex]; if (island.memberCount == 0) { //Found an orphan island left over from merge procedures or removal procedures. //Shoo it on out. simulationIslands.FastRemoveAt(deactivationIslandIndex); GiveBackIsland(island); } else { island.TryToDeactivate(); numberOfEntitiesDeactivated += island.memberCount; } ++numberOfIslandsChecked; } }
void RemoveStaleOverlaps() { //We don't need to do any synchronization or queueing here; just remove everything directly. ApplySolverUpdateableChangesDirectly = true; //Remove stale objects. for (int i = narrowPhasePairs.count - 1; i >= 0; i--) { var pair = narrowPhasePairs.Elements[i]; //A stale overlap is a pair which has not been updated, but not because of inactivity. //Pairs between two inactive shapes are not updated because the broad phase does not output overlaps //between inactive entries. We need to keep such pairs around, otherwise when they wake up, lots of extra work //will be needed and quality will suffer. //The classic stale overlap is two objects which have moved apart. Because the bounding boxes no longer overlap, //the broad phase does not generate an overlap for them. Obviously, we should get rid of such a pair. //Any such pair will have at least one active member. Having velocity requires activity and teleportation will activate the object. //There's another sneaky kind of stale overlap. Consider a sleeping dynamic object on a Terrain. The Terrain, being a static object, //is considered inactive. The sleeping dynamic object is also inactive. Now, remove the sleeping dynamic object. //Both objects are still considered inactive. But the pair is clearly stale- one of its members doesn't even exist anymore! //This has nasty side effects, like retaining memory. To solve this, also check to see if either member does not belong to the simulation. if (pair.NeedsUpdate && //If we didn't receive an update in the previous narrow phase run and... (pair.broadPhaseOverlap.entryA.IsActive || pair.broadPhaseOverlap.entryB.IsActive || //one of us is active or.. pair.broadPhaseOverlap.entryA.BroadPhase == null || pair.broadPhaseOverlap.entryB.BroadPhase == null)) //one of us doesn't exist anymore... { //Get rid of the pair! if (RemovingPair != null) RemovingPair(pair); narrowPhasePairs.FastRemoveAt(i); overlapMapping.Remove(pair.BroadPhaseOverlap); //The clean up will issue an order to get rid of the solver updateable if it is active. //To avoid a situation where the solver updateable outlives the pair but is available for re-use //because of the factory giveback here, the updateable is removed directly (ApplySolverUpdateableChangesDirectly = true). pair.CleanUp(); pair.Factory.GiveBack(pair); } else { pair.NeedsUpdate = true; } } ApplySolverUpdateableChangesDirectly = false; }
///<summary> /// Removes a connection reference from the member. ///</summary> ///<param name="connection">Reference to remove.</param> ///<param name="index">Index of the connection in this member's list</param> internal void RemoveConnectionReference(SimulationIslandConnection connection, int index) { if (connections.Count > index) { connections.FastRemoveAt(index); if (connections.Count > index) { connections.Elements[index].SetListIndex(this, index); } } }
void RemoveStaleOverlaps() { //Remove stale objects. for (int i = narrowPhasePairs.count - 1; i >= 0; i--) { var pair = narrowPhasePairs.Elements[i]; if (pair.NeedsUpdate && //Overlap will not be refreshed if entries are inactive, but shouldn't remove narrow phase pair. (pair.BroadPhaseOverlap.entryA.IsActive || pair.BroadPhaseOverlap.entryB.IsActive)) { narrowPhasePairs.FastRemoveAt(i); OnRemovePair(pair); } else { pair.NeedsUpdate = true; } } }
internal void RemovePair(CollidablePairHandler pair, ref int index) { if (pairs.count > index) { pairs.FastRemoveAt(index); if (pairs.count > index) { var endPair = pairs.Elements[index]; if (endPair.CollidableA == this) { endPair.listIndexA = index; } else { endPair.listIndexB = index; } } } index = -1; }
public override void Update(float dt) { for (int i = removedEntities.Count - 1; i >= 0; --i) { if (random.NextDouble() < 0.2) { var entity = removedEntities[i]; addedEntities.Add(entity); Space.Add(entity); removedEntities.FastRemoveAt(i); } } for (int i = addedEntities.Count - 1; i >= 0; --i) { if (random.NextDouble() < 0.02) { var entity = addedEntities[i]; removedEntities.Add(entity); Space.Remove(entity); addedEntities.FastRemoveAt(i); } } if (Game.MouseInput.MiddleButton != Microsoft.Xna.Framework.Input.ButtonState.Pressed) { for (int i = 0; i < 20; i++) { var entity = addedEntities[random.Next(addedEntities.Count)]; entity.Position = new Vector3( (float)(random.NextDouble() - 0.5f) * width, (float)(random.NextDouble() - 0.5f) * height, (float)(random.NextDouble() - 0.5f) * length); } } base.Update(dt); }
private static void RemoveInsidePoints(RawList <Vector3> points, RawList <int> triangleIndices, RawList <int> outsidePoints) { RawList <int> insidePoints = CommonResources.GetIntList(); //We're going to remove points from this list as we go to prune it down to the truly inner points. insidePoints.AddRange(outsidePoints); outsidePoints.Clear(); for (int i = 0; i < triangleIndices.Count && insidePoints.Count > 0; i += 3) { //Compute the triangle's plane in point-normal representation to test other points against. Vector3 normal; FindNormal(triangleIndices, points, i, out normal); Vector3 p = points.Elements[triangleIndices.Elements[i]]; for (int j = insidePoints.Count - 1; j >= 0; --j) { //Offset from the triangle to the current point, tested against the normal, determines if the current point is visible //from the triangle face. Vector3 offset; Vector3.Subtract(ref points.Elements[insidePoints.Elements[j]], ref p, out offset); float dot; Vector3.Dot(ref offset, ref normal, out dot); //If it's visible, then it's outside! if (dot > 0) { //This point is known to be on the outside; put it on the outside! outsidePoints.Add(insidePoints.Elements[j]); insidePoints.FastRemoveAt(j); } } } CommonResources.GiveBack(insidePoints); }
/// <summary> /// Constructs a new demo. /// </summary> /// <param name="game">Game owning this demo.</param> public MutableStaticGroupTestDemo(DemosGame game) : base(game) { //Creating a bunch of separate StaticMeshes or kinematic Entity objects for an environment can pollute the broad phase. //This is because the broad phase implementation doesn't have guarantees about what elements can collide, so it has to //traverse the acceleration structure all the way down to pairs to figure it out. That can get expensive! //Individual objects, like StaticMeshes, can have very complicated geometry without hurting the broad phase because the broad phase //has no knowledge of the thousands of triangles in the mesh. The StaticMesh itself knows that the triangles within the mesh //never need to collide, so it never needs to test them against each other. //Similarly, the StaticGroup can be given a bunch of separate collidables. The broad phase doesn't directly know about these child collidables- //it only sees the StaticGroup. The StaticGroup knows that the things inside it can't ever collide with each other, so no tests are needed. //This avoids the performance problem! //To demonstrate, we'll be creating a set of static objects and giving them to a group to manage. var collidables = new List<Collidable>(); //Start with a whole bunch of boxes. These are entity collidables, but without entities! float xSpacing = 6; float ySpacing = 6; float zSpacing = 6; //NOTE: You might notice this demo takes a while to start, especially on the Xbox360. Do not fear! That's due to the creation of the graphics data, not the physics. //The physics can handle over 100,000 static objects pretty easily. The graphics, not so much :) //Try disabling the game.ModelDrawer.Add() lines and increasing the number of static objects. int xCount = 15; int yCount = 7; int zCount = 15; var random = new Random(5); for (int i = 0; i < xCount; i++) { for (int j = 0; j < yCount; j++) { for (int k = 0; k < zCount; k++) { //Create a transform and the instance of the mesh. var collidable = new ConvexCollidable<BoxShape>(new BoxShape((float)random.NextDouble() * 6 + .5f, (float)random.NextDouble() * 6 + .5f, (float)random.NextDouble() * 6 + .5f)); //This EntityCollidable isn't associated with an entity, so we must manually tell it where to sit by setting the WorldTransform. //This also updates its bounding box. collidable.WorldTransform = new RigidTransform( new Vector3(i * xSpacing - xCount * xSpacing * .5f, j * ySpacing + 3, k * zSpacing - zCount * zSpacing * .5f), Quaternion.CreateFromAxisAngle(Vector3.Normalize(new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble())), (float)random.NextDouble() * 100)); collidables.Add(collidable); } } } //Now create a bunch of instanced meshes too. xSpacing = 6; ySpacing = 6; zSpacing = 6; xCount = 10; yCount = 2; zCount = 10; Vector3[] vertices; int[] indices; ModelDataExtractor.GetVerticesAndIndicesFromModel(game.Content.Load<Model>("fish"), out vertices, out indices); var meshShape = new InstancedMeshShape(vertices, indices); for (int i = 0; i < xCount; i++) { for (int j = 0; j < yCount; j++) { for (int k = 0; k < zCount; k++) { //Create a transform and the instance of the mesh. var transform = new AffineTransform( new Vector3((float)random.NextDouble() * 6 + .5f, (float)random.NextDouble() * 6 + .5f, (float)random.NextDouble() * 6 + .5f), Quaternion.CreateFromAxisAngle(Vector3.Normalize(new Vector3((float)random.NextDouble(), (float)random.NextDouble(), (float)random.NextDouble())), (float)random.NextDouble() * 100), new Vector3(i * xSpacing - xCount * xSpacing * .5f, j * ySpacing + 50, k * zSpacing - zCount * zSpacing * .5f)); var mesh = new InstancedMesh(meshShape, transform); //Making the triangles one-sided makes collision detection a bit more robust, since the backsides of triangles won't try to collide with things //and 'pull' them back into the mesh. mesh.Sidedness = TriangleSidedness.Counterclockwise; collidables.Add(mesh); } } } var ground = new ConvexCollidable<BoxShape>(new BoxShape(200, 1, 200)); ground.WorldTransform = new RigidTransform(new Vector3(0, -3, 0), Quaternion.Identity); collidables.Add(ground); var group = new StaticGroup(collidables); var removed = new RawList<Collidable>(); var contained = new RawList<Collidable>(); group.Shape.CollidableTree.CollectLeaves(contained); for (int i = 0; i < 100000; ++i) { for (int collidableIndex = contained.Count - 1; collidableIndex >= 0; --collidableIndex) { if (random.NextDouble() < 0.6) { group.Shape.Remove(contained[collidableIndex]); removed.Add(contained[collidableIndex]); contained.FastRemoveAt(collidableIndex); } } for (int collidableIndex = removed.Count - 1; collidableIndex >= 0; --collidableIndex) { if (random.NextDouble() < 0.4) { group.Shape.Add(removed[collidableIndex]); contained.Add(removed[collidableIndex]); removed.FastRemoveAt(collidableIndex); } } } for (int i = 0; i < contained.Count; ++i) { game.ModelDrawer.Add(contained[i]); } Space.Add(group); //Create a bunch of dynamic boxes to drop on the staticswarm. xCount = 8; yCount = 3; zCount = 8; xSpacing = 3f; ySpacing = 5f; zSpacing = 3f; for (int i = 0; i < xCount; i++) for (int j = 0; j < zCount; j++) for (int k = 0; k < yCount; k++) { Space.Add(new Box(new Vector3( xSpacing * i - (xCount - 1) * xSpacing / 2f, 100 + k * (ySpacing), 2 + zSpacing * j - (zCount - 1) * zSpacing / 2f), 1, 1, 1, 10)); } game.Camera.Position = new Vector3(0, 60, 90); }
/// <summary> /// Identifies the indices of points in a set which are on the outer convex hull of the set. /// </summary> /// <param name="points">List of points in the set.</param> /// <param name="outputTriangleIndices">List of indices into the input point set composing the triangulated surface of the convex hull. /// Each group of 3 indices represents a triangle on the surface of the hull.</param> public static void GetConvexHull(RawList <Vector3> points, RawList <int> outputTriangleIndices) { if (points.Count == 0) { throw new ArgumentException("Point set must have volume."); } RawList <int> outsidePoints = CommonResources.GetIntList(); if (outsidePoints.Capacity < points.Count - 4) { outsidePoints.Capacity = points.Count - 4; } //Build the initial tetrahedron. //It will also give us the location of a point which is guaranteed to be within the //final convex hull. We can use this point to calibrate the winding of triangles. //A set of outside point candidates (all points other than those composing the tetrahedron) will be returned in the outsidePoints list. //That list will then be further pruned by the RemoveInsidePoints call. Vector3 insidePoint; ComputeInitialTetrahedron(points, outsidePoints, outputTriangleIndices, out insidePoint); //Compute outside points. RemoveInsidePoints(points, outputTriangleIndices, outsidePoints); var edges = CommonResources.GetIntList(); var toRemove = CommonResources.GetIntList(); var newTriangles = CommonResources.GetIntList(); //We're now ready to begin the main loop. while (outsidePoints.Count > 0) { //While the convex hull is incomplete... for (int k = 0; k < outputTriangleIndices.Count; k += 3) { //Find the normal of the triangle Vector3 normal; FindNormal(outputTriangleIndices, points, k, out normal); //Get the furthest point in the direction of the normal. int maxIndexInOutsideList = GetExtremePoint(ref normal, points, outsidePoints); int maxIndex = outsidePoints.Elements[maxIndexInOutsideList]; Vector3 maximum = points.Elements[maxIndex]; //If the point is beyond the current triangle, continue. Vector3 offset; Vector3.Subtract(ref maximum, ref points.Elements[outputTriangleIndices.Elements[k]], out offset); float dot; Vector3.Dot(ref normal, ref offset, out dot); if (dot > 0) { //It's been picked! Remove the maximum point from the outside. outsidePoints.FastRemoveAt(maxIndexInOutsideList); //Remove any triangles that can see the point, including itself! edges.Clear(); toRemove.Clear(); for (int n = outputTriangleIndices.Count - 3; n >= 0; n -= 3) { //Go through each triangle, if it can be seen, delete it and use maintainEdge on its edges. if (IsTriangleVisibleFromPoint(outputTriangleIndices, points, n, ref maximum)) { //This triangle can see it! //TODO: CONSIDER CONSISTENT WINDING HAPPYTIMES MaintainEdge(outputTriangleIndices[n], outputTriangleIndices[n + 1], edges); MaintainEdge(outputTriangleIndices[n], outputTriangleIndices[n + 2], edges); MaintainEdge(outputTriangleIndices[n + 1], outputTriangleIndices[n + 2], edges); //Because fast removals are being used, the order is very important. //It's pulling indices in from the end of the list in order, and also ensuring //that we never issue a removal order beyond the end of the list. outputTriangleIndices.FastRemoveAt(n + 2); outputTriangleIndices.FastRemoveAt(n + 1); outputTriangleIndices.FastRemoveAt(n); } } //Create new triangles. for (int n = 0; n < edges.Count; n += 2) { //For each edge, create a triangle with the extreme point. newTriangles.Add(edges[n]); newTriangles.Add(edges[n + 1]); newTriangles.Add(maxIndex); } //Only verify the windings of the new triangles. VerifyWindings(newTriangles, points, ref insidePoint); outputTriangleIndices.AddRange(newTriangles); newTriangles.Clear(); //Remove all points from the outsidePoints if they are inside the polyhedron RemoveInsidePoints(points, outputTriangleIndices, outsidePoints); //The list has been significantly messed with, so restart the loop. break; } } } CommonResources.GiveBack(outsidePoints); CommonResources.GiveBack(edges); CommonResources.GiveBack(toRemove); CommonResources.GiveBack(newTriangles); }
public override void Update(float dt) { //Add previously removed characters. for (int i = removedCharacters.Count - 1; i >= 0; --i) { if (random.NextDouble() < 0.2) { Space.Add(removedCharacters[i]); removedCharacters.FastRemoveAt(i); } } for (int i = removedSphereCharacters.Count - 1; i >= 0; --i) { if (random.NextDouble() < 0.2) { Space.Add(removedSphereCharacters[i]); removedSphereCharacters.FastRemoveAt(i); } } //Tell all the characters to run around randomly. for (int i = 0; i < characters.Count; i++) { if (characters[i].Space != null) { if (random.NextDouble() < 0.02) { removedCharacters.Add(characters[i]); Space.Remove(characters[i]); } else { characters[i].HorizontalMotionConstraint.MovementDirection = new Vector2((float)(random.NextDouble() * 2 - 1), (float)(random.NextDouble() * 2 - 1)); if (random.NextDouble() < .01f) { characters[i].Jump(); } var next = random.NextDouble(); if (next < .01) { //Note: The character's graphic won't represent the crouching process properly since we're not remove/readding it. if (next < .005f && characters[i].StanceManager.CurrentStance == Stance.Standing) { characters[i].StanceManager.DesiredStance = Stance.Crouching; } else { characters[i].StanceManager.DesiredStance = Stance.Standing; } } } } } //Tell the sphere characters to run around too. for (int i = 0; i < sphereCharacters.Count; i++) { if (sphereCharacters[i].Space != null) { if (random.NextDouble() < 0.02) { removedSphereCharacters.Add(sphereCharacters[i]); Space.Remove(sphereCharacters[i]); } else { sphereCharacters[i].HorizontalMotionConstraint.MovementDirection = new Vector2((float)(random.NextDouble() * 2 - 1), (float)(random.NextDouble() * 2 - 1)); if (random.NextDouble() < .01f) { sphereCharacters[i].Jump(); } } } } base.Update(dt); }
private static void MaintainEdge(int a, int b, RawList<int> edges) { bool contained = false; int index = 0; for (int k = 0; k < edges.Count; k += 2) { if ((edges[k] == a && edges[k + 1] == b) || (edges[k] == b && edges[k + 1] == a)) { contained = true; index = k; } } //If it isn't present, add it to the edge list. if (!contained) { edges.Add(a); edges.Add(b); } else { //If it is present, that means both edge-connected triangles were deleted now, so get rid of it. edges.FastRemoveAt(index + 1); edges.FastRemoveAt(index); } }
/// <summary> /// Identifies the indices of points in a set which are on the outer convex hull of the set. /// </summary> /// <param name="points">List of points in the set.</param> /// <param name="outputTriangleIndices">List of indices composing the triangulated surface of the convex hull. /// Each group of 3 indices represents a triangle on the surface of the hull.</param> public static void GetConvexHull(RawList<Vector3> points, RawList<int> outputTriangleIndices) { if (points.Count == 0) { throw new ArgumentException("Point set must have volume."); } RawList<int> outsidePoints = CommonResources.GetIntList(); if (outsidePoints.Capacity < points.Count - 4) outsidePoints.Capacity = points.Count - 4; //Build the initial tetrahedron. //It will also give us the location of a point which is guaranteed to be within the //final convex hull. We can use this point to calibrate the winding of triangles. //A set of outside point candidates (all points other than those composing the tetrahedron) will be returned in the outsidePoints list. //That list will then be further pruned by the RemoveInsidePoints call. Vector3 insidePoint; ComputeInitialTetrahedron(points, outsidePoints, outputTriangleIndices, out insidePoint); //Compute outside points. RemoveInsidePoints(points, outputTriangleIndices, outsidePoints); var edges = CommonResources.GetIntList(); var toRemove = CommonResources.GetIntList(); var newTriangles = CommonResources.GetIntList(); //We're now ready to begin the main loop. while (outsidePoints.Count > 0) { //While the convex hull is incomplete... for (int k = 0; k < outputTriangleIndices.Count; k += 3) { //Find the normal of the triangle Vector3 normal; FindNormal(outputTriangleIndices, points, k, out normal); //Get the furthest point in the direction of the normal. int maxIndexInOutsideList = GetExtremePoint(ref normal, points, outsidePoints); int maxIndex = outsidePoints.Elements[maxIndexInOutsideList]; Vector3 maximum = points.Elements[maxIndex]; //If the point is beyond the current triangle, continue. Vector3 offset; Vector3.Subtract(ref maximum, ref points.Elements[outputTriangleIndices.Elements[k]], out offset); float dot; Vector3.Dot(ref normal, ref offset, out dot); if (dot > 0) { //It's been picked! Remove the maximum point from the outside. outsidePoints.FastRemoveAt(maxIndexInOutsideList); //Remove any triangles that can see the point, including itself! edges.Clear(); toRemove.Clear(); for (int n = outputTriangleIndices.Count - 3; n >= 0; n -= 3) { //Go through each triangle, if it can be seen, delete it and use maintainEdge on its edges. if (IsTriangleVisibleFromPoint(outputTriangleIndices, points, n, ref maximum)) { //This triangle can see it! //TODO: CONSIDER CONSISTENT WINDING HAPPYTIMES MaintainEdge(outputTriangleIndices[n], outputTriangleIndices[n + 1], edges); MaintainEdge(outputTriangleIndices[n], outputTriangleIndices[n + 2], edges); MaintainEdge(outputTriangleIndices[n + 1], outputTriangleIndices[n + 2], edges); //Because fast removals are being used, the order is very important. //It's pulling indices in from the end of the list in order, and also ensuring //that we never issue a removal order beyond the end of the list. outputTriangleIndices.FastRemoveAt(n + 2); outputTriangleIndices.FastRemoveAt(n + 1); outputTriangleIndices.FastRemoveAt(n); } } //Create new triangles. for (int n = 0; n < edges.Count; n += 2) { //For each edge, create a triangle with the extreme point. newTriangles.Add(edges[n]); newTriangles.Add(edges[n + 1]); newTriangles.Add(maxIndex); } //Only verify the windings of the new triangles. VerifyWindings(newTriangles, points, ref insidePoint); outputTriangleIndices.AddRange(newTriangles); newTriangles.Clear(); //Remove all points from the outsidePoints if they are inside the polyhedron RemoveInsidePoints(points, outputTriangleIndices, outsidePoints); //The list has been significantly messed with, so restart the loop. break; } } } CommonResources.GiveBack(outsidePoints); CommonResources.GiveBack(edges); CommonResources.GiveBack(toRemove); CommonResources.GiveBack(newTriangles); }
/// <summary> /// Constructs a new demo. /// </summary> /// <param name="game">Game owning this demo.</param> public MutableStaticGroupTestDemo(DemosGame game) : base(game) { //Creating a bunch of separate StaticMeshes or kinematic Entity objects for an environment can pollute the broad phase. //This is because the broad phase implementation doesn't have guarantees about what elements can collide, so it has to //traverse the acceleration structure all the way down to pairs to figure it out. That can get expensive! //Individual objects, like StaticMeshes, can have very complicated geometry without hurting the broad phase because the broad phase //has no knowledge of the thousands of triangles in the mesh. The StaticMesh itself knows that the triangles within the mesh //never need to collide, so it never needs to test them against each other. //Similarly, the StaticGroup can be given a bunch of separate collidables. The broad phase doesn't directly know about these child collidables- //it only sees the StaticGroup. The StaticGroup knows that the things inside it can't ever collide with each other, so no tests are needed. //This avoids the performance problem! //To demonstrate, we'll be creating a set of static objects and giving them to a group to manage. var collidables = new List <Collidable>(); //Start with a whole bunch of boxes. These are entity collidables, but without entities! Fix64 xSpacing = 6; Fix64 ySpacing = 6; Fix64 zSpacing = 6; //NOTE: You might notice this demo takes a while to start, especially on the Xbox360. Do not fear! That's due to the creation of the graphics data, not the physics. //The physics can handle over 100,000 static objects pretty easily. The graphics, not so much :) //Try disabling the game.ModelDrawer.Add() lines and increasing the number of static objects. int xCount = 15; int yCount = 7; int zCount = 15; var random = new Random(5); for (int i = 0; i < xCount; i++) { for (int j = 0; j < yCount; j++) { for (int k = 0; k < zCount; k++) { //Create a transform and the instance of the mesh. var collidable = new ConvexCollidable <BoxShape>(new BoxShape((Fix64)random.NextDouble() * 6 + .5m, (Fix64)random.NextDouble() * 6 + .5m, (Fix64)random.NextDouble() * 6 + .5m)); //This EntityCollidable isn't associated with an entity, so we must manually tell it where to sit by setting the WorldTransform. //This also updates its bounding box. collidable.WorldTransform = new RigidTransform( new Vector3(i * xSpacing - xCount * xSpacing * .5m, j * ySpacing + 3, k * zSpacing - zCount * zSpacing * .5m), Quaternion.CreateFromAxisAngle(Vector3.Normalize(new Vector3((Fix64)random.NextDouble(), (Fix64)random.NextDouble(), (Fix64)random.NextDouble())), (Fix64)random.NextDouble() * 100)); collidables.Add(collidable); } } } //Now create a bunch of instanced meshes too. xSpacing = 6; ySpacing = 6; zSpacing = 6; xCount = 10; yCount = 2; zCount = 10; Vector3[] vertices; int[] indices; ModelDataExtractor.GetVerticesAndIndicesFromModel(game.Content.Load <Model>("fish"), out vertices, out indices); var meshShape = new InstancedMeshShape(vertices, indices); for (int i = 0; i < xCount; i++) { for (int j = 0; j < yCount; j++) { for (int k = 0; k < zCount; k++) { //Create a transform and the instance of the mesh. var transform = new AffineTransform( new Vector3((Fix64)random.NextDouble() * 6 + .5m, (Fix64)random.NextDouble() * 6 + .5m, (Fix64)random.NextDouble() * 6 + .5m), Quaternion.CreateFromAxisAngle(Vector3.Normalize(new Vector3((Fix64)random.NextDouble(), (Fix64)random.NextDouble(), (Fix64)random.NextDouble())), (Fix64)random.NextDouble() * 100), new Vector3(i * xSpacing - xCount * xSpacing * .5m, j * ySpacing + 50, k * zSpacing - zCount * zSpacing * .5m)); var mesh = new InstancedMesh(meshShape, transform); //Making the triangles one-sided makes collision detection a bit more robust, since the backsides of triangles won't try to collide with things //and 'pull' them back into the mesh. mesh.Sidedness = TriangleSidedness.Counterclockwise; collidables.Add(mesh); } } } var ground = new ConvexCollidable <BoxShape>(new BoxShape(200, 1, 200)); ground.WorldTransform = new RigidTransform(new Vector3(0, -3, 0), Quaternion.Identity); collidables.Add(ground); var group = new StaticGroup(collidables); var removed = new RawList <Collidable>(); var contained = new RawList <Collidable>(); group.Shape.CollidableTree.CollectLeaves(contained); for (int i = 0; i < 100000; ++i) { for (int collidableIndex = contained.Count - 1; collidableIndex >= 0; --collidableIndex) { if (random.NextDouble() < 0.6) { group.Shape.Remove(contained[collidableIndex]); removed.Add(contained[collidableIndex]); contained.FastRemoveAt(collidableIndex); } } for (int collidableIndex = removed.Count - 1; collidableIndex >= 0; --collidableIndex) { if (random.NextDouble() < 0.4) { group.Shape.Add(removed[collidableIndex]); contained.Add(removed[collidableIndex]); removed.FastRemoveAt(collidableIndex); } } } for (int i = 0; i < contained.Count; ++i) { game.ModelDrawer.Add(contained[i]); } Space.Add(group); //Create a bunch of dynamic boxes to drop on the staticswarm. xCount = 8; yCount = 3; zCount = 8; xSpacing = 3; ySpacing = 5; zSpacing = 3; for (int i = 0; i < xCount; i++) { for (int j = 0; j < zCount; j++) { for (int k = 0; k < yCount; k++) { Space.Add(new Box(new Vector3( xSpacing * i - (xCount - 1) * xSpacing / 2, 100 + k * (ySpacing), 2 + zSpacing * j - (zCount - 1) * zSpacing / 2), 1, 1, 1, 10)); } } } game.Camera.Position = new Vector3(0, 60, 90); }