Goal: Implementation of a base class representing circular boundaries.
Required concepts: object classes, constructors/destructors, operator overloading.
These concepts are explained in lessons 16 to 19. The exercises in series 19 contain material similar to what you need to program.Useful files : partie1.zip
Instructions for writing the REPONSES file: see the forum.
The instructions below assume that you have created the directory cpp/projet. Adapt the commands to the location of your project.
Copy the provided archive partie1.zip to your cpp/projet directory and unzip it using the command unzip partie1.zip (type man unzip in a terminal for more details on this command). If you are working on your own machines:
===============================================================================
All tests passed (60 assertions in 9 test cases)
The meaning of this execution will be explained below. The archive provided contains:
To increase the modularity of your project, prototyping methods and declaring attributes should be placed in a file with the extension .hpp while their definitions should be placed in a file with the extension .cpp.
Here are some useful tips about compilation:How to avoid multiple inclusions:
#pragma once
File naming convention:
This is, of course, only a convention.
This part of the project consists of a single module (component).
The aim here is to put your initial knowledge of object-oriented programming into practice by programming a utility class representing circular boundaries in a two-dimensional space.
The idea is that the entities in the program (bacteria, nutrient sources, etc.) will evolve in a two-dimensional space.
These entities will encounter each other, and it will be necessary to be able to test this fact in a simple way: for example, if a bacterium encounters a nutrient source, it will be able to consume it. It is therefore important to be able to test that the encounter takes place.
Testing the collision/encounter of two objects can be very complex depending on their actual shapes. In this project, we will use a simplification that consists of approximating the shape of any entity using a circular outline. Collision tests will then be performed simply in relation to these outlines, as illustrated in the image below:
|
The collision test between a bacterium and a nutrient source is simplified by approximating each of these entities using a circular outline (red circles). |
The CircularBoundary class that you are asked to program is used to represent the circular boundaries used for collision testing.
You will give the CircularBoundary class the following members:
a constructor initializing the position and radius of the circular object using a Vec2d and a double passed as parameters (in that order).
“getters” and “setters” (getPosition and setPosition as well as getRadius and setRadius) for the position and radius. The getter for the position will return a constant reference to a Vec2d ;
a copy constructor;
a move method adding a Vec2d passed as an argument to the position of the current instance (see the + operator of Vec2d) ;
a contains method taking a CircularBoundary, other as an argument and returning true if other is inside the current instance and false otherwise. Given two CircularBoundary objects, a and b, a is inside b if:
a method isColliding that takes a CircularBoundary, other, as an argument and returns true if other collides with the current instance and false otherwise. Let there be two CircularBoundary, a and b, a collides with b if the distance between the centers of a and b is less than or equal to the sum of the radii of a and b;
a method contains that takes a point (of type Vec2d) as an argument and returns true if the point is inside the current instance and false otherwise. A point p is inside a CircularBoundary body if the distance between p and body is less than or equal to the radius of body;
the operator > can be used as follows:
body1 > body2returning true if body2 is inside body1 and false otherwise. The objects body1 and body2 are of type CircularBoundary;
the operator & can be used as follows:
body1 & body2returns true if body2 collides with body1 and false otherwise. The objects body1 and body2 are of type CircularBoundary;
the operator > can be used as follows:
body > pointreturning true if point is inside body and false otherwise. The objects body are of type CircularBoundary and point are of type Vec2d;
[Question Q1.1] How can you code the three operators described above while avoiding any duplication of code in the CircularBoundary.cpp file? Think about it and answer this question in your REPONSES file.
CircularBoundary: position = <position> radius = <radius>where <position> is the position of the CircularBoundary and <radius> is its radius.
[Question Q1.2] Which overload do you choose for the operators you were asked to code (internal or external) and how do you justify this choice? Think about it and answer this question in your REPONSES file.
[Question Q1.3] Which method arguments, among those you were asked to code above, do you think it would be wise to pass by constant reference? Answer this question in your REPONSES file.
[Question Q1.4] Which of the methods you were asked to code do you think should be declared as const? Answer this question in your REPONSES file.
To test this part of the project, you have:
the circularBoundaryTest target defined in the partie1/src/CMakeLists.txt file, which calls several files;
This target allows you to run the tests/UnitTests/CircularBoundaryTest.cpp test program. This program calls the various functions you coded in the CircularBoundary class to check that they are correct.
Take a quick look at the CMakeLists.txt file to see how this target is defined:
add_executable(circularBoundaryTest ${TEST_DIR}/UnitTests/CircularBoundaryTest.cpp ${CB_SOURCES} ${CACHE_SOURCES})
configure_sfml_executable(circularBoundaryTest ....)
allowing all CircularBoundaryTest tests to be run.
===============================================================================
All tests passed (60 assertions in 9 test cases)
(and which shows that the class we have provided successfully passes a number of tests)
To understand how these tests work, open the file src/tests/UnitTests/CircularBoundaryTest.cpp and examine it.
This test program uses a specific framework called Catch, which allows you to perform what is known in programming jargon as unit tests. This involves writing test scenarios for different coded features.
Below, we explain how these tests work.
The test program provided in CircularBoundaryTest.cpp defines a scenario (“test case”) for testing collision detection features using the methods you were asked to code in the CircularBoundary class.
The scenario contains a number of messages (labeled with the keywords GIVEN, THEN, or AND_THEN) that describe the conditions under which the various tests are performed and their results. These messages will only be displayed if a test fails.
If all the tests in a scenario run correctly, you will simply see a message similar to this:
=============================================================================== All tests passed
The tests consist of verifying a number of assertions expressed using features such as CHECK and CHECK_FALSE.
Let's examine, for example, the first test in the scenario (lines 35 to 50).
This test begins by constructing two identical circular contours (b1 and b2 at position (1,1) and radius 2), which are supposed to collide if your code is correct (this is what is displayed using THEN). To test that your code works correctly for this case, you need to perform four CHECK :
As you can see, a CHECK succeeds if the condition passed to it as an argument returns true (similarly, a CHECK_FALSE succeeds if the condition passed to it as an argument returns false).
Suppose your isColliding method is poorly coded. Running the test program CircularBoundaryTest.cpp will display:
-------------------------------------------------------------------------------
Scenario: Collision/IsInside with CircularBoundary
Given: Two identical bodies
Then: they collide
-------------------------------------------------------------------------------
src/Tests/UnitTests/CircularBoundaryTest.cpp:26
...............................................................................
src/Tests/UnitTests/CircularBoundaryTest.cpp:37: FAILED:
CHECK( b1.isColliding(b2) )
with expansion:
false
src/Tests/UnitTests/CircularBoundaryTest.cpp:38: FAILED:
CHECK( b2.isColliding(b1) )
with expansion:
false
The test therefore displays the name associated with the scenario, the messages associated with GIVEN and THEN to indicate what we have as input (“Two identical bodies”) and what is supposed to be true in this case (“they collide”), followed by the list of failed assertions and their lines.
Running the CircularBoundaryTest target should produce the following output:
CircularBoundary: position = (1, 1), radius = 2 =============================================================================== All tests passed (42 assertions in 1 test case)
Note: in QtCreator, this output is visible in the “Application output” window.
This indicates that 42 assertions (CHECK or CHECK_FALSE ) were successfully run as part of a single scenario (“1 test case”).
Note that if the tested features are not present in your code, running the test will cause compilation errors.
For example, suppose that isColliding (taking a CircularBoundary as an argument) is not coded in your code, you will then get an error message such as
error: no matching function for call to 'Body::isColliding(Body&)'
CHECK(b1.isColliding(b2));
Running the target CircularBoundaryTest should produce the following output:
CircularBoundary: position = (1, 1), radius = 2 =============================================================================== All tests passed (42 assertions in 1 test case)
The message CircularBoundary: position = (1, 1), radius = 2 should also be displayed to show that line 44 is executing correctly (i.e., your overload of the << does what is expected of it).