Bodies-> Remove from active set. Assert

Discuss any questions about BEPUphysics or problems encountered.
Post Reply
Darkon76
Posts: 23
Joined: Mon Mar 27, 2017 12:33 pm

Bodies-> Remove from active set. Assert

Post by Darkon76 » Thu Jul 11, 2019 3:10 pm

Hello
I'm porting my project from bepu 1 to bepu 2. I was really happy when I manually added and removed objects and everything worked correctly.

I knew that it was the time for real testing. So I created a big scene, with over 1k moving objects. It worked with out a problem in bepu 1. But at bepu 2 I had consistent assert at Bodies -> RemoveFromActiveSet(int activeBodyIndex)

At line 187

Code: Select all

 Debug.Assert(HandleToLocation[movedBodyHandle].SetIndex == 0 && HandleToLocation[movedBodyHandle].Index == movedBodyIndex);


It trigger because the Index is different than the movedBodyIndex.

I'm adding and removing the object before calling the Simulation.Timestep.

What can produce that error? and how can I avoid it?

Regards
Marlon.

Norbo
Site Admin
Posts: 4755
Joined: Tue Jul 04, 2006 4:45 am

Re: Bodies-> Remove from active set. Assert

Post by Norbo » Thu Jul 11, 2019 6:58 pm

Is this using the latest commit on master (54fe6d75)?

Can you reproduce the bug in the demos or a stripped down minimalist standalone project? This sort of issue is a bit hard to guess at.

Darkon76
Posts: 23
Joined: Mon Mar 27, 2017 12:33 pm

Re: Bodies-> Remove from active set. Assert

Post by Darkon76 » Thu Jul 11, 2019 8:33 pm

Ok, I will port my working setting to the demo.

Darkon76
Posts: 23
Joined: Mon Mar 27, 2017 12:33 pm

Re: Bodies-> Remove from active set. Assert

Post by Darkon76 » Mon Jul 15, 2019 4:17 pm

Hi, Currently I failed to reproduce the bug at the demo project. But I notice that there is a stack overflow at the tree, if a lot of objects are inserted at the same time.

Edit: Lowering the number of iterations from 50 to 25 at line 56 avoid the issue.

Code: Select all

using System.Numerics;
using BepuPhysics;
using BepuPhysics.Collidables;
using BepuUtilities;
using BepuUtilities.Collections;
using DemoContentLoader;
using DemoRenderer;
using Demos.Port;
using DemoUtilities;
using OpenTK.Input;
using Quaternion = BepuUtilities.Quaternion;

namespace Demos.Demos
{
    public class BigConveyorDemo : Demo
    {
        private BodyDescription _boxDescription;
        private BodyDescription _horizontalConveyorDescription;
        private BodyDescription _verticalConveyorDescription;

        private const float ConveyorSpeed = 2f;

        private readonly Quaternion[] _orientations = new Quaternion[4];

        private QuickList<int> _createdObjects;


        private const float _conveyorHalfWidth = 1.5f;
        private const float _horizontalHalfLenght = 80f;
        private const float _verticalHalfLenght = 4f;



        public override void Initialize(ContentArchive content, Camera camera)
        {
            camera.Position = new Vector3(-30, 8, -60);
            camera.Yaw = MathHelper.Pi * 3f / 4;
            camera.Pitch = 0;

            Simulation = Simulation.Create(BufferPool, new DemoNarrowPhaseCallbacks(), new DemoPoseIntegratorCallbacks(new Vector3(0f,-9.81f,0f))
               // , new CustomPositionLastTimestepper()
                );

            _createdObjects = new QuickList<int>(1000, BufferPool);

            _boxDescription = CreateBoxDescription(1, 1, 1);
            _horizontalConveyorDescription = CreateConveyorDescription(_horizontalHalfLenght);
            _verticalConveyorDescription = CreateConveyorDescription(_verticalHalfLenght);
            
            _orientations[0] = Quaternion.Identity;
            Quaternion.CreateFromYawPitchRoll(-1.5708f, 0, 0, out _orientations[1]);
            Quaternion.CreateFromYawPitchRoll(-3.1416f, 0, 0, out _orientations[2]);
            Quaternion.CreateFromYawPitchRoll(-4.7124f, 0, 0, out _orientations[3]);

            var startPosition = Vector3.Zero;
            for (int i = 0; i < 50; i++)
            {
                CreateLevel(ref startPosition);
            }

        }


        private BodyDescription CreateConveyorDescription(float halfLenght)
        {
            var description =  CreateBoxDescription(halfLenght * 2, 1f, _conveyorHalfWidth * 2);
            //description.ConveyorSettings.IsLinearConveyor = true;
            //description.ConveyorSettings.ConveyorVelocity = new Vector3(ConveyorSpeed, 0,0);
            description.LocalInertia = new BodyInertia();
            description.Activity.SleepThreshold = -1;
            return description;
        }
        private BodyDescription CreateBoxDescription(float width, float height, float lenght )
        {
            var boxShape = new Box(width, height, lenght);
            var boxShapeIndex = Simulation.Shapes.Add(boxShape);
            boxShape.ComputeInertia(1, out var boxInertia);
            Symmetric3x3.Scale(boxInertia.InverseInertiaTensor, .5f, out boxInertia.InverseInertiaTensor);
            var boxDescription = new BodyDescription()
            {
                LocalInertia = boxInertia,
                Pose = new RigidPose
                {
                    Position = Vector3.Zero,
                    Orientation = Quaternion.Identity
                },
                Activity = new BodyActivityDescription { MinimumTimestepCountUnderThreshold = 32, SleepThreshold = .01f },
                Collidable = new CollidableDescription { Shape = boxShapeIndex, SpeculativeMargin = .1f },
            };

            return boxDescription;
        }



        private void CreateLevel(ref Vector3 position)
        {
            var boxPosition = position;

            CreateHorizontalConveyor(ref position, ref _orientations[0]);
            boxPosition.Y += 2;
            for (var i = -(_horizontalHalfLenght-1); i <= (_horizontalHalfLenght - 1); i += 2)
            {
                boxPosition.X = i;
                Instantiate(_boxDescription, ref boxPosition,ref _orientations[0]);
            }
            
            position += new Vector3(_horizontalHalfLenght + _conveyorHalfWidth, 0, _verticalHalfLenght - _conveyorHalfWidth);
            CreateVerticalConveyor(ref position, ref _orientations[1]);
            position += new Vector3(-_horizontalHalfLenght + _conveyorHalfWidth, 0, _verticalHalfLenght + _conveyorHalfWidth);
            CreateHorizontalConveyor(ref position, ref _orientations[2]);

            boxPosition = position;
            boxPosition.Y += 2;
            for (var i = -(_horizontalHalfLenght - 1); i <= (_horizontalHalfLenght - 1); i += 2)
            {
                boxPosition.X = i;
                Instantiate(_boxDescription, ref boxPosition, ref _orientations[0]);
            }

            position += new Vector3(-_horizontalHalfLenght - _conveyorHalfWidth, 0, _verticalHalfLenght - _conveyorHalfWidth);
            CreateVerticalConveyor(ref position, ref _orientations[1]);
            position += new Vector3(_horizontalHalfLenght - _conveyorHalfWidth, 0, _verticalHalfLenght + _conveyorHalfWidth);

        }



        private void CreateHorizontalConveyor(ref Vector3 position, ref Quaternion orientation)
        {
            Instantiate(_horizontalConveyorDescription, ref position, ref orientation);
        }

        private void CreateVerticalConveyor(ref Vector3 position, ref Quaternion orientation)
        {
            Instantiate(_verticalConveyorDescription, ref position, ref orientation);
        }

        private void Instantiate(BodyDescription bodyReference, ref Vector3 position, ref Quaternion orientation)
        {
            bodyReference.Pose.Position = position;
            bodyReference.Pose.Orientation = orientation;
            _createdObjects.Add(Simulation.Bodies.Add(bodyReference), BufferPool);
        }

        private float _spawnCount = 0;
        private float _spawnTime = 2f;

        public override void Update(Window window, Camera camera, Input input, float dt)
        {
            base.Update(window, camera, input, dt);
            #region  KeyInput

            if (input.WasPushed(Key.Z))
            {
                var boxPosition = new Vector3(-_horizontalHalfLenght + 1, 2,0);
                var identity = Quaternion.Identity;
                Instantiate(_boxDescription, ref boxPosition, ref identity);
            }

            _spawnCount += dt;
            if (_spawnCount >= _spawnTime)
            {
                _spawnCount -= _spawnTime;
                var boxPosition = new Vector3(-_horizontalHalfLenght + 1 , 2, 0);
                var identity = Quaternion.Identity;
                Instantiate(_boxDescription, ref boxPosition, ref identity);
            }

            //I know it is unoptimized but it is the easier way to delete the falling boxes.
            for (var i = _createdObjects.Count - 1; i > 0; --i)
            {
                var bodiIndex = _createdObjects[i];
                var reference = new BodyReference(bodiIndex, Simulation.Bodies);
                if (reference.Pose.Position.Y < -10)
                {
                    Simulation.Bodies.Remove(bodiIndex);
                    _createdObjects.RemoveAt(i);
                }
            }
            #endregion
        }
    }
}

Norbo
Site Admin
Posts: 4755
Joined: Tue Jul 04, 2006 4:45 am

Re: Bodies-> Remove from active set. Assert

Post by Norbo » Tue Jul 16, 2019 12:52 am

This is caused by a pathological insertion order in the broad phase which turns the tree into more of a giant linked list, followed by a testing routine that relies on stack memory.

I tweaked the insertion heuristic a little to encourage a little more balance. It's still not great (greedy insertions will always have some issues), but it at least won't trigger a stack overflow so easily.

Fortunately, once the simulation is actually running, tree refinement will very rapidly fix the tree.

There are a few more ways around this:
1) Make additions more random. They don't have to be fully random, but uniform ordered insertions are more likely to trigger corner cases in greedy additions.
2) Batch add objects to the broad phase. This isn't exposed by the API, but it is something I'd like to do. It would help with sleep/wake performance too.
3) Make all the tests immune to stack overflow. This is also something I'm planning to do when I get around to revamping the tree implementation.

Post Reply