Work on 2 axis the same way

I have been coding this 2D physic engine for quite some time,,

I work on the many part of these 2 axis ( x and y ) in the same manner is just that one is an x and one is y

So there is a lot of duplicate code

If there is a bug in one part I have to fix the other axis too which becomes a hazzle because I have to fix the duplicates too

The sample code from my program
1
2
3
4
5
6
7
8
9
10
11
12

for( auto it =bodies.begin(); it != bodies.end(); ++ it ){
   sf::Vector2f velocity = it->getVelocity();
   if( velocity.x != 0 ){
     // do some friction force calculation
   }
   else if( something_thing_else ){
      // do something else
   }

   // then work on the same thing to y axis
}


It is even more of a hazzle when I work with 4 quadrant, I have 4 duplicate code each

How do I write a function for both axis ?
Use a full vector2D class (with all arithmetic operators etc)

Do everything in vectors.

e.g. frictive force (a vector) = (some function of magnitude of velocity etc) * (-normalise(velocity))

Read up on your maths so that you never have to use separate components again.
I guess I should study some more,,,

My school haven't and won't teach physic in 2 dimension

Any suggestion on basic physic tutorial in 2 dimension ?
Hey, since your school won't teach you about it, i think maybe you should get something like Textbook and learn it yourself, it won't be very difficult since i managed to learn C and C++ myself. Textbook will just be fine and easy enough for beginners (as it should be :).

BTW, i was thinking about writing an engine by myself once, but i soon realise the difficulty in this kind of project, a lot things need to be considered before you moved on code. Though i think that 2D physic and 2D math won't be a problem for me(even 3D, if not a tough problem), maybe we can work together if you like?

Anyway, cheer up!
C++ is like my 4th language after javascript, php and python, it's been 3 years since I started so I think I've covered all the basic of C++

Anyway why would you want to work with me ?

I cut corners a lot and I am in the middle of reviewing and refactoring my code
there is a bug I couldn't find so I might as well re read every thing


If we could work together please email me ( code000123[at]gmail.com )
I'll send you what I've made

Are you working on a project yourself ?
> I work on the many part of these 2 axis ( x and y ) in the same manner is just that one is an x and one is y
> So there is a lot of duplicate code
> How do I write a function for both axis ?

Assuming that much of the code is similar to the sample code that was posted -there is a sequence which contains the coordinates along each axis ( x and y ), we want to iterate through this sequence and do identical things with either the x coordinates or the y coordinates):

1. Define iterators which can iterate over either the x or the y coordinates in the sequence.
2. Write code that is polymorphic on these iterators.

For instance, using boost::transform_iterator<>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <functional>
#include <boost/iterator/transform_iterator.hpp>
#include <utility>
#include <iterator>

struct velocity
{
    // ...
    int x ;
    int y ;
};

const std::function< int& (velocity&) > get_x = [] ( velocity& v ) -> int& { return v.x ; } ;
const std::function< int& (velocity&) > get_y = [] ( velocity& v ) -> int& { return v.y ; } ;

template < typename ITERATOR > using x_iterator = boost::transform_iterator< decltype(get_x), ITERATOR > ;
template < typename ITERATOR > using y_iterator = boost::transform_iterator< decltype(get_y), ITERATOR > ;

template < typename SEQUENCE > using ITERATOR_TYPE = decltype( std::begin( std::declval<SEQUENCE>() ) ) ;

template < typename SEQUENCE > x_iterator< ITERATOR_TYPE<SEQUENCE> > x_begin( SEQUENCE&& seq )
{ return x_iterator< ITERATOR_TYPE<SEQUENCE> >( std::begin(seq), get_x ) ; }

template < typename SEQUENCE > x_iterator< ITERATOR_TYPE<SEQUENCE> > x_end( SEQUENCE&& seq )
{ return x_iterator< ITERATOR_TYPE<SEQUENCE> >( std::end(seq), get_x ) ; }

template < typename SEQUENCE > y_iterator< ITERATOR_TYPE<SEQUENCE> > y_begin( SEQUENCE&& seq )
{ return y_iterator< ITERATOR_TYPE<SEQUENCE> >( std::begin(seq), get_y ) ; }

template < typename SEQUENCE > y_iterator< ITERATOR_TYPE<SEQUENCE> > y_end( SEQUENCE&& seq )
{ return y_iterator< ITERATOR_TYPE<SEQUENCE> >( std::end(seq), get_y ) ; }

// and likewise for reverse iterators, const iterators and const_reverse_iterators

#include <vector>
#include <iostream>
#include <algorithm>

int main()
{
    std::vector<velocity> seq { {0,1}, {2,3}, {4,5}, {6,7}, {8,9} } ;
    const auto print = [] ( int v ) { std::cout << v << ' ' ; } ;

    std::for_each( x_begin(seq), x_end(seq), print ) ;
    std::cout << '\n' ;

    std::for_each( y_begin(seq), y_end(seq), print ) ;
    std::cout << '\n' ;

    std::for_each( x_begin(seq), x_end(seq), [] ( int& v ) { v += 20 ; } ) ;
    std::for_each( y_begin(seq), y_end(seq), [] ( int& v ) { v *= 11 ; } ) ;

    std::for_each( x_begin(seq), x_end(seq), print ) ;
    std::cout << '\n' ;

    std::for_each( y_begin(seq), y_end(seq), print ) ;
    std::cout << '\n' ;
}

Output:
0 2 4 6 8
1 3 5 7 9
20 22 24 26 28
11 33 55 77 99

wow, it looks difficult

is it strange to ask
why use STL algorithm when the normal way ( using "for" ) is ok too ?

I got inspiration before too like change some code to this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

void update_velocity( float& velocity, float force, float kforce, float inverseMass, float dt );

void do_update( float dt )
{
	Body *body;
	for( auto it = Db.begin(); it != Db.end(); ++ it )
	{
		body = &toBody( it );
		
		force = body->getForce();
		kforce = body->getFrictionForce();
		velocity = body->getVelocity();
		inverseMass = body->getInverseMass();

		// - work on x axis
			update_velocity( velocity.x, force.x, kforce.x, inverseMass, dt );
		// - work on y axis
			update_velocity( velocity.y, force.y, kforce.y, inverseMass, dt );

		// - Apply it to the object
			body->setVelocity( velocity );
	}

}


Though I cannot apply it to the other part of the code just because the detail is simply different in other part of the code

tell me where did your solution did better than mine ?

and also Lambda function is new, haven't try experimenting with them,,, Just have doubt if I can write a lambda function
> tell me where did your solution did better than mine ?.

It is not a solution, let alone a better solution. It was just a design idea, and it is not usable except under the assumption that was stated prior to the code.


> Though I cannot apply it to the other part of the code just because the detail is simply different in other part of the code

Perhaps you could post a few more of those cases - where everything else is identical except for either the x coordinate or the y coordinate being used.
Something like this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73

for( auto it = Database.begin(); it != Database.end(); ++ it ){
	body = &(it->second.body);
	delta = body->getVelocity() * dt;

	if( delta.x < -0.f ) {
		min = numeric_limits<float>::infinity();
		for( auto it_ = Database.begin(); it_ != Database.end(); ++ it_ ){
			if( it == it_ ) continue;
			body_comp = &(it_->second.body);

			if( body_comp->max_y() < body->min_y() || body_comp->min_y() > body->max_y() ) continue;
			temporary = body->min_x() - body_comp->max_x(); // [Minor difference]
			if( 0.f < temporary && temporary < min ){
				min 	= temporary;
				min_it	= it_;
			}
		}


		if( min <= -delta.x ) // [Minor difference]
		{
			if( min > tolerance ){
				body->translate_x( -( min - tolerance ) ); // [Minor difference]
			}

			ResolveCollision( it->second.body, min_it->second.body, sf::Vector2f( -1.f, 0.f ) ); // [Minor difference]
			ResolveFricionY( it->second.body, min_it->second.body );

			it->		second.collision_root_left.insert( min_it ); // [Minor difference]
			min_it->	second.collision_root_right.insert( it ); // [Minor difference]
		}
		else {
			body->translate_x( delta.x );
		}
	}
	else if( 0.f < delta.x ) {
		min = numeric_limits<float>::infinity();
		for( auto it_ = Database.begin(); it_ != Database.end(); ++ it_ ){
			if( it == it_ ) continue;
			body_comp = &(it_->second.body);

			if( body_comp->max_y() < body->min_y() || body_comp->min_y() > body->max_y() ) continue;
			temporary = body_comp->min_x() - body->max_x(); // [Minor difference]

			if( 0.f < temporary && temporary < min ){
				min 	= temporary;
				min_it	= it_;
			}
		}


		if( min <= delta.x ) // if there is collision // [Minor difference]
		{
			if( min > tolerance ){
				body->translate_x( min - tolerance ); // [Minor difference]
			}

			ResolveCollision( it->second.body, min_it->second.body, sf::Vector2f( 1.f, 0.f ) ); // [Minor difference]
			ResolveFricionY( it->second.body, min_it->second.body );


			it->		second.collision_root_right.insert( min_it ); // [Minor difference]
			min_it->	second.collision_root_left.insert( it ); // [Minor difference]
		}

		else { // if there is no collision
			body->translate_x( delta.x );
		}
	}
	
	// do the same thing to y axis
}


I have point out the minor difference, sorry if I miss any
This code is just for x axis,
it does almost the same thing to y axis but with many minor differences

There is actually more to the code, there are experimental stuff and debug codes, but I won't post them here as they will be too long and unnecesarry ( I think )
Last edited on
> This code is just for x axis,
> it does almost the same thing to y axis but with many minor differences

It would certainly have helped if you had indicated what at least one of those 'minor' differences looked like.

Anyway, the basic idea remains the same.

1. Create a set of abstractions for symmetric operations on x and y. For instance,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <functional>

template < typename T, typename R, typename... ARGS >
std::function< R( T& ) > member( R (T::*fn)(ARGS...), ARGS... args )
{ return std::bind( fn, std::placeholders::_1, args... ) ; }

template < typename T, typename R, typename... ARGS >
std::function< R( const T& ) > member( R (T::*fn)(ARGS...) const, ARGS... args )
{ return std::bind( fn, std::placeholders::_1, args... ) ; }

template < typename T, typename R, typename... ARGS >
std::function< R() > member( T& object, R (T::*fn)(ARGS...), ARGS... args )
{ return std::bind( fn, std::ref(object), args... ) ; }

template < typename T, typename R, typename... ARGS >
std::function< R() > member( const T& object, R (T::*fn)(ARGS...) const, ARGS... args )
{ return std::bind( fn, std::cref(object), args... ) ; }

template < typename T, typename R >
std::function< R( T& ) > member( R (T::*p) )
{ return std::bind( [p] ( T& object ) { return object.*p ; }, std::placeholders::_1 ) ; }

template < typename T, typename R >
std::function< R() > member( const T& object, R (T::*p) )
{ auto ref_wrapper = std::cref(object) ; return [ ref_wrapper, p ] () { return ref_wrapper.get().*p ; } ; }


2. Write code which is polymorphic so that we can use identical code, with the difference being the abstraction used - either for x or for y.

For instance, the first part of your code could be written (for both x and y) this way (in practice, the 'minor difference' would also have to be abstracted away):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
template < typename ITERATOR, typename MIN, typename MAX, typename MIN2, typename MAX2 >
std::pair< double, ITERATOR > find_min( ITERATOR iter, ITERATOR begin, ITERATOR end,
                                        MIN min, MAX max, MIN2 min2, MAX2 max2 )
{
    auto minimum = std::numeric_limits<float>::infinity();
    decltype(minimum) min = minimum ;
    ITERATOR min_it = end ;

    for( ; begin != end ; ++begin )
    {
        if( iter == begin ) continue ;
        auto body_comp = &(it_->second.body);
        auto& bc = *body_comp ;
        if( max2(bc) < min2(bc) || min2(bc) > max2(bc) ) continue ;
        // ignoring the [Minor difference] (don't know what it is)
        auto temporary = min(bc) - max(bc) ; 
    }
    return { min, min_it } ;
}

// ...
{
    for( auto it = Database.begin(); it != Database.end(); ++ it )
    {
        auto body = &(it->second.body);
        auto delta = body->getVelocity() * dt;
        using body_t = decltype(body) ;

        if( delta.x < -0.f )
        {
            double min_for_x, min_for_y ;
            decltype(it) min_it_for_x, min_it_for_y ;

            std::tie( find_min( it, Database.begin(), Database.end(),
                                member(&body_t::min_x), member(&body_t::max_x),
                                member(&body_t::min_y), member(&body_t::max_y ) ),
                                min_for_x, min_it_for_x ) ;

            // use min_for_x, min_it_for_x

            std::tie( find_min( it, Database.begin(), Database.end(),
                                member(&body_t::min_y), member(&body_t::max_y),
                                member(&body_t::min_x), member(&body_t::max_x ) ),
                                min_for_y, min_it_for_y ) ;
                                
            // use min_for_y, min_it_for_y
        }

        // ...
    }
    // ...
}



Topic archived. No new replies allowed.