0

I have a mid-term assignment in which we conduct 3 sets of unit tests with documentation etc for a program from our course. The program I chose is a physics simulation.

Within this program, there are two classes, Thing and World. I am able to independently create these objects. I tried adding the Thing object to the World object by creating a std::vector<Thing> things, and then creating a function to add the Thing to the vector of things. However, when I do that, it seems as though the World object creates its own copy of the Thing, because when I change the Things position, the version of the Thing in the things vector remains the same.

Please provide some guidance on the matter, I feel my problem might be with how I use pointers in this situation.

 void testBoundaryBounce()
 {
      //Create the world
      World world{10.0f, 10.0f, 1.0f};
      //Thing, 2 units away from boundary
      Thing thing{8.0f, 5.0f, 1.0f};
      //Add thing to world (adding it the the things vector)
      world.addThing(&thing);
      //Apply force that should trigger bounce back to x = 7
      thing.applyForce(1.0f, 0.0f);
      //Updating thing so that movement takes effect
      thing.update();
      //Running world update to account for collisions, bounces etc
      world.update();

      std::cout << "thing x : " << thing.getX() << std::endl;

      CPPUNIT_ASSERT(thing.getX() == 7);
 }


Thing::Thing(float x, float y, float radius)
: x{x}, y{y}, dX{0}, dY{0}, radius{radius}
{

}


World::World(float width, float height, float gravity)
: width{width}, height{height}, gravity{gravity}
{
    std::vector<Thing> things;
}



void World::addThing(Thing* thing)
{
    float thingX = thing->getX();
    float thingY = thing->getY();
    float thingRad = thing->getRad();

    std::cout << "Radius : " << thingRad << std::endl;

    if (thingX + thingRad > width || thingX - thingRad <= 0 || thingY + thingRad > 
    height|| thingY - thingRad <= 0)
    {
        std::cout << "Thing is out of bounds or is too large" << std::endl;
    }
    else {
        std::cout << "Thing is good" << std::endl;
        things.push_back(*thing);
    }
}

void World::update()
{
    for (Thing& thing : things)
    {
        thing.update();
        float thingX = thing.getX();
        float thingY = thing.getY();
        float thingRad = thing.getRad();
        float worldGrav = this->gravity;

        std::cout << "thing x: " << thingX << std::endl;
        std::cout << "thing rad: " << thingRad << std::endl;

        //World Boundary Bounces
        if (thingX + thingRad >= width)
        {
            std::cout << "Bounce left" << std::endl;
            thing.applyForce(-2.0f, 0.0f);
            thing.update();
        }
        if (thingX + thingRad <= 0)
        {
            thing.applyForce(2.0f, 0.0f);
            thing.update();
            std::cout << "Bounce right" << std::endl;
        }
        if (thingY + thingRad >= height)
        {
            thing.applyForce(0.0f, -2.0f);
            thing.update();
            std::cout << "Bounce up" << std::endl;
        }
        if (thingY - thingRad <= 0)
        {
            thing.applyForce(0.0f, 2.0f);
            thing.update();
            std::cout << "Bounce down" << std::endl;
        }
        //Thing Collision Bounces
        for (Thing& otherthing : things)
        {
            float thing2X = otherthing.getX();
            float thing2Y = otherthing.getY();
            float thing2Rad = otherthing.getRad();
            if (thingX + thingRad == thing2X + thing2Rad && thingY + thingRad == 
                thing2Y + thing2Rad)
            {
                thing.applyForce(-2.0f, -2.0f);
                thing.update();
                otherthing.applyForce(2.0f, 2.0f);
                otherthing.update();
            }
        }
        //Gravitational Pull
        thing.applyForce(0.0f, worldGrav);
    }
}
James Z
  • 12,209
  • 10
  • 24
  • 44
  • Just to clarify, when I say "thing.getX();" in the unit test after applying a force, I get the correct x value. But when I parse over the vector things, the values I get from the vector "things" are unaltered by forces I've applied. – Keean Ferreira Jan 05 '22 at 14:49
  • It adds a copy of your object, because you told it to do so with this line `things.push_back(*thing);`. Do you want to have only pointers in your `World` object? – mch Jan 05 '22 at 14:52
  • Avoid pointers, Read this : https://www.learncpp.com/cpp-tutorial/references/, then this : https://www.learncpp.com/cpp-tutorial/intro-to-smart-pointers-move-semantics/. The you can use : addThing(Thing& thing) and use things.push_back(std::move(thing)) to avoid copies. – Pepijn Kramer Jan 05 '22 at 15:00
  • `std::vector things;` does not initialize the `things` member of the class. It simply declares a new function-local variable with the same name. You initialize the member, the same way you do for the others, in the member-initializer-list: `: width{width}, height{height}, gravity{gravity}, things{}`. (The order should match the declarations in the class.) [Enable compiler warnings](https://stackoverflow.com/questions/57842756/why-should-i-always-enable-compiler-warnings), which would tell you this. (This is not related to your problem though.) – user17732522 Jan 05 '22 at 15:04
  • @PepijnKramer I don't think they are concerned with the copy operation, but with the fact that `std::vector` does not store references to objects. – user17732522 Jan 05 '22 at 15:14
  • @user17732522 Could be, still is useful to learn why things are copied. And then there is this : (https://stackoverflow.com/questions/922360/why-cant-i-make-a-vector-of-references) – Pepijn Kramer Jan 05 '22 at 15:26
  • 1
    It may be that the `World` maintaining the only copy is the right thing to do, and you can give your `Thing`'s speed and momentum *before* adding them. Then you could just let the `World` do all the work. – Galik Jan 05 '22 at 15:38

2 Answers2

1

Its right in the definition of void push_back (const value_type& val);...

Adds a new element at the end of the vector, after its current last element. The content of val is copied (or moved) to the new element.

so when you call things.push_back(*thing);, you are adding a new element to the 'things' vector which is a copy of the value pointed to by the thing pointer.

You want to change your vector to hold pointers to Thing types instead:

std::vector<Thing *> things;

and add the pointers instead of copies:

things.push_back(thing);

Note you will later have to access fields via -> instead of ., or you can create a reference to it such as:

    for (Thing* pt: things)
    {
        Thing& thing = *pt; 
        thing.update();
        //etc...
Sam
  • 109
  • 5
  • @TedLyngmo `Thing`s declared in `testBoundaryBounce()` are valid until that function returns, no? – Sam Jan 05 '22 at 15:41
  • 1
    @Sam they are valid until the end of the scope that declares them – Caleth Jan 05 '22 at 15:41
  • @Sam Yes, you are correct. I didn't notice that `world` was also a local object. My bad! Deleted ramblings! :-) – Ted Lyngmo Jan 05 '22 at 15:43
  • @Sam Thanks a bunch, I can confirm that your recommendation worked for me, the correct values are now showing :) – Keean Ferreira Jan 05 '22 at 16:00
  • @KeeanFerreira Up to the left of the answer, there's a gray checkmark. If this answer helped you to solve the problem, you can click that checkmark to accept the answer. – Ted Lyngmo Jan 05 '22 at 22:34
0

Instead of constructing a Thing and copying it into world, you could have World construct the Things that it owns

void testBoundaryBounce()
 {
      //Create the world
      World world{10.0f, 10.0f, 1.0f};
      //Thing, 2 units away from boundary
      Thing * thing = world.addThing(8.0f, 5.0f, 1.0f);
      //Apply force that should trigger bounce back to x = 7
      thing->applyForce(1.0f, 0.0f);
      //Running world update to account for collisions, bounces etc
      //Implies updating all the things
      world.update();

      std::cout << "thing x : " << thing.getX() << std::endl;

      CPPUNIT_ASSERT(thing.getX() == 7);
 }


Thing::Thing(float x, float y, float radius)
: x{x}, y{y}, dX{0}, dY{0}, radius{radius}
{

}


World::World(float width, float height, float gravity)
: width{width}, height{height}, gravity{gravity}
{
    std::vector<Thing> things;
}



Thing * World::addThing(float x, float y, float radius)
{
    std::cout << "Radius : " << radius << std::endl;

    if ((x + radius > width) || (x - radius <= 0) || (y + radius > 
    height) || (y - radius <= 0))
    {
        std::cout << "Thing is out of bounds or is too large" << std::endl;
        return nullptr;
    }
    else 
    {
        std::cout << "Thing is good" << std::endl;
        return &things.emplace_back(x, y, radius);
    }
}

void World::update()
{
    for (Thing& thing : things)
    {
        thing.update();
        float thingX = thing.getX();
        float thingY = thing.getY();
        float thingRad = thing.getRad();
        float worldGrav = this->gravity;

        std::cout << "thing x: " << thingX << std::endl;
        std::cout << "thing rad: " << thingRad << std::endl;

        //World Boundary Bounces
        if (thingX + thingRad >= width)
        {
            std::cout << "Bounce left" << std::endl;
            thing.applyForce(-2.0f, 0.0f);
            thing.update();
        }
        if (thingX + thingRad <= 0)
        {
            thing.applyForce(2.0f, 0.0f);
            thing.update();
            std::cout << "Bounce right" << std::endl;
        }
        if (thingY + thingRad >= height)
        {
            thing.applyForce(0.0f, -2.0f);
            thing.update();
            std::cout << "Bounce up" << std::endl;
        }
        if (thingY - thingRad <= 0)
        {
            thing.applyForce(0.0f, 2.0f);
            thing.update();
            std::cout << "Bounce down" << std::endl;
        }
        //Thing Collision Bounces
        for (Thing& otherthing : things)
        {
            float thing2X = otherthing.getX();
            float thing2Y = otherthing.getY();
            float thing2Rad = otherthing.getRad();
            if (thingX + thingRad == thing2X + thing2Rad && thingY + thingRad == 
                thing2Y + thing2Rad)
            {
                thing.applyForce(-2.0f, -2.0f);
                thing.update();
                otherthing.applyForce(2.0f, 2.0f);
                otherthing.update();
            }
        }
        //Gravitational Pull
        thing.applyForce(0.0f, worldGrav);
    }
}

As an aside, everything bounces off of itself every update

Caleth
  • 52,200
  • 2
  • 44
  • 75