protected Simulation(BufferPool bufferPool, SimulationAllocationSizes initialAllocationSizes) { BufferPool = bufferPool; Shapes = new Shapes(bufferPool, initialAllocationSizes.ShapesPerType); BroadPhase = new BroadPhase(bufferPool, initialAllocationSizes.Bodies, initialAllocationSizes.Bodies + initialAllocationSizes.Statics); Bodies = new Bodies(bufferPool, Shapes, BroadPhase, initialAllocationSizes.Bodies, initialAllocationSizes.Islands, initialAllocationSizes.ConstraintCountPerBodyEstimate); Statics = new Statics(bufferPool, Shapes, Bodies, BroadPhase, initialAllocationSizes.Statics); Solver = new Solver(Bodies, BufferPool, 8, initialCapacity: initialAllocationSizes.Constraints, initialIslandCapacity: initialAllocationSizes.Islands, minimumCapacityPerTypeBatch: initialAllocationSizes.ConstraintsPerTypeBatch); constraintRemover = new ConstraintRemover(BufferPool, Bodies, Solver); Sleeper = new IslandSleeper(Bodies, Solver, BroadPhase, constraintRemover, BufferPool); Awakener = new IslandAwakener(Bodies, Statics, Solver, BroadPhase, Sleeper, bufferPool); Statics.awakener = Awakener; Solver.awakener = Awakener; Bodies.Initialize(Solver, Awakener); PoseIntegrator = new PoseIntegrator(Bodies, Shapes, BroadPhase); SolverBatchCompressor = new BatchCompressor(Solver, Bodies); BodyLayoutOptimizer = new BodyLayoutOptimizer(Bodies, BroadPhase, Solver, bufferPool); ConstraintLayoutOptimizer = new ConstraintLayoutOptimizer(Bodies, Solver); }
/// <summary> /// Constructs a simulation supporting dynamic movement and constraints with the specified narrow phase callbacks. /// </summary> /// <param name="bufferPool">Buffer pool used to fill persistent structures and main thread ephemeral resources across the engine.</param> /// <param name="narrowPhaseCallbacks">Callbacks to use in the narrow phase.</param> /// <param name="poseIntegratorCallbacks">Callbacks to use in the pose integrator.</param> /// <param name="timestepper">Timestepper that defines how the simulation state should be updated.</param> /// <param name="solverIterationCount">Number of iterations the solver should use.</param> /// <param name="solverFallbackBatchThreshold">Number of synchronized batches the solver should maintain before falling back to a lower quality jacobi hybrid solver.</param> /// <param name="initialAllocationSizes">Allocation sizes to initialize the simulation with. If left null, default values are chosen.</param> /// <returns>New simulation.</returns> public static Simulation Create <TNarrowPhaseCallbacks, TPoseIntegratorCallbacks>( BufferPool bufferPool, TNarrowPhaseCallbacks narrowPhaseCallbacks, TPoseIntegratorCallbacks poseIntegratorCallbacks, ITimestepper timestepper, int solverIterationCount = 8, int solverFallbackBatchThreshold = 64, SimulationAllocationSizes?initialAllocationSizes = null) where TNarrowPhaseCallbacks : struct, INarrowPhaseCallbacks where TPoseIntegratorCallbacks : struct, IPoseIntegratorCallbacks { if (initialAllocationSizes == null) { initialAllocationSizes = new SimulationAllocationSizes { Bodies = 4096, Statics = 4096, ShapesPerType = 128, ConstraintCountPerBodyEstimate = 8, Constraints = 16384, ConstraintsPerTypeBatch = 256 }; } var simulation = new Simulation(bufferPool, initialAllocationSizes.Value, solverIterationCount, solverFallbackBatchThreshold, timestepper); var poseIntegrator = new PoseIntegrator <TPoseIntegratorCallbacks>(simulation.Bodies, simulation.Shapes, simulation.BroadPhase, poseIntegratorCallbacks); simulation.PoseIntegrator = poseIntegrator; var narrowPhase = new NarrowPhase <TNarrowPhaseCallbacks>(simulation, DefaultTypes.CreateDefaultCollisionTaskRegistry(), DefaultTypes.CreateDefaultSweepTaskRegistry(), narrowPhaseCallbacks, initialAllocationSizes.Value.Islands + 1); DefaultTypes.RegisterDefaults(simulation.Solver, narrowPhase); simulation.NarrowPhase = narrowPhase; simulation.Sleeper.pairCache = narrowPhase.PairCache; simulation.Awakener.pairCache = narrowPhase.PairCache; simulation.Solver.pairCache = narrowPhase.PairCache; simulation.BroadPhaseOverlapFinder = new CollidableOverlapFinder <TNarrowPhaseCallbacks>(narrowPhase, simulation.BroadPhase); //We defer initialization until after all the other simulation bits are constructed. poseIntegrator.Callbacks.Initialize(simulation); narrowPhase.Callbacks.Initialize(simulation); return(simulation); }
//TODO: I wonder if people will abuse the dt-as-parameter to the point where we should make it a field instead, like it effectively was in v1. /// <summary> /// Performs one timestep of the given length. /// </summary> /// <remarks> /// Be wary of variable timesteps. They can harm stability. Whenever possible, keep the timestep the same across multiple frames unless you have a specific reason not to. /// </remarks> /// <param name="dt">Duration of the time step in time.</param> public void Timestep(float dt, IThreadDispatcher threadDispatcher = null) { ProfilerClear(); ProfilerStart(this); //Note that the first behavior-affecting stage is actually the pose integrator. This is a shift from v1, where collision detection went first. //This is a tradeoff: //1) Any externally set velocities will be integrated without input from the solver. The v1-style external velocity control won't work as well- //the user would instead have to change velocities after the pose integrator runs. This isn't perfect either, since the pose integrator is also responsible //for updating the bounding boxes used for collision detection. //2) By bundling bounding box calculation with pose integration, you avoid redundant pose and velocity memory accesses. //3) Generated contact positions are in sync with the integrated poses. //That's often helpful for gameplay purposes- you don't have to reinterpret contact data when creating graphical effects or positioning sound sources. //TODO: This is something that is possibly worth exposing as one of the generic type parameters. Users could just choose the order arbitrarily. //Or, since you're talking about something that happens once per frame instead of once per collision pair, just provide a simple callback. //(Or maybe an enum even?) //#1 is a difficult problem, though. There is no fully 'correct' place to change velocities. We might just have to bite the bullet and create a //inertia tensor/bounding box update separate from pose integration. If the cache gets evicted in between (virtually guaranteed unless no stages run), //this basically means an extra 100-200 microseconds per frame on a processor with ~20GBps bandwidth simulating 32768 bodies. //Note that the reason why the pose integrator comes first instead of, say, the solver, is that the solver relies on world space inertias calculated by the pose integration. //If the pose integrator doesn't run first, we either need //1) complicated on demand updates of world inertia when objects are added or local inertias are changed or //2) local->world inertia calculation before the solver. ProfilerStart(PoseIntegrator); PoseIntegrator.Update(dt, BufferPool, threadDispatcher); ProfilerEnd(PoseIntegrator); ProfilerStart(BroadPhase); BroadPhase.Update(threadDispatcher); ProfilerEnd(BroadPhase); ProfilerStart(BroadPhaseOverlapFinder); BroadPhaseOverlapFinder.DispatchOverlaps(threadDispatcher); ProfilerEnd(BroadPhaseOverlapFinder); ProfilerStart(NarrowPhase); NarrowPhase.Flush(threadDispatcher, threadDispatcher != null && Deterministic); ProfilerEnd(NarrowPhase); ProfilerStart(Solver); if (threadDispatcher == null) { Solver.Update(dt); } else { Solver.MultithreadedUpdate(threadDispatcher, BufferPool, dt); } ProfilerEnd(Solver); //Note that constraint optimization should be performed after body optimization, since body optimization moves the bodies- and so affects the optimal constraint position. //TODO: The order of these optimizer stages is performance relevant, even though they don't have any effect on correctness. //You may want to try them in different locations to see how they impact cache residency. ProfilerStart(BodyLayoutOptimizer); if (threadDispatcher == null) { BodyLayoutOptimizer.IncrementalOptimize(); } else { BodyLayoutOptimizer.IncrementalOptimize(BufferPool, threadDispatcher); } ProfilerEnd(BodyLayoutOptimizer); ProfilerStart(ConstraintLayoutOptimizer); ConstraintLayoutOptimizer.Update(BufferPool, threadDispatcher); ProfilerEnd(ConstraintLayoutOptimizer); ProfilerStart(SolverBatchCompressor); SolverBatchCompressor.Compress(BufferPool, threadDispatcher, threadDispatcher != null && Deterministic); ProfilerEnd(SolverBatchCompressor); ProfilerEnd(this); }