Rect and Vec2 traits

The Partridge Family were neither partridges nor a family. Discuss.
Post Reply
albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Rect and Vec2 traits

Post by albinopapa » August 10th, 2020, 6:30 am

Code: Select all

template<typename Vec> struct vector2_member_access_traits;

template<typename VecAccessTraits> struct vector2_traits {
	using access_traits = VecAccessTraits;
	using vector_type = typename VecAccessTraits::vector_type;
	using scalar_type = decltype( access_traits::x( std::declval<VecAccessTraits::vector_type>() ) );

	static constexpr auto construct( scalar_type x_, scalar_type y_ )noexcept {
		return access_traits::construct( x_, y_ );
	}

	static constexpr auto x( vector_type const& vec )noexcept {
		return access_traits::x( vec );
	}
	static constexpr void x( vector_type& vec, scalar_type value )noexcept {
		access_traits::x( vec, value );
	}

	static constexpr auto y( vector_type const& vec )noexcept {
		return access_traits::y( vec );
	}
	static constexpr void y( vector_type& vec, scalar_type value )noexcept {
		access_traits::y( vec, value );
	}

	static constexpr auto dot( vector_type const& lhs, vector_type const& rhs )noexcept {
		return ( x( lhs ) * x( rhs ) ) + ( y( lhs ) * y( rhs ) );
	}
	static constexpr auto length_sqr( vector_type const& lhs )noexcept {
		return dot( lhs, lhs );
	}
	static auto length( vector_type const& vec )noexcept {
		return std::sqrt( length_sqr( vec ) );
	}
	static auto normalize( vector_type const& vec )noexcept {
		static constexpr auto zero = scalar_type( 0 );
		if( x( vec ) == zero && y( vec ) == zero )return vec;

		if constexpr( std::is_floating_point_v<scalar_type> ) {
			const auto len = scalar_type( 1 ) / length( vec );
			return vector_type{ x( vec ) * len, y( vec ) * len };
		}
		else {
			const auto len = length( vec );
			return vector_type{ x( vec ) / len, y( vec ) / len };
		}
	}
};

Code: Select all

template<typename Rect> struct rect_member_access_traits;

template<typename RectAccessTraits>
struct rect_traits {
	using access_traits = RectAccessTraits;
	using rect_type = typename RectAccessTraits::rect_type;
	using scalar_type = decltype( access_traits::left( std::declval<rect_type>() ) );

	static constexpr auto construct( scalar_type left_, scalar_type top_, scalar_type right_, scalar_type bottom_ )noexcept {
		return access_traits::construct( left_, top_, right_, bottom_ );
	}
	static constexpr auto left( rect_type const& rect )noexcept {
		return access_traits::left( rect );
	}
	static constexpr auto top( rect_type const& rect )noexcept {
		return access_traits::top( rect );
	}
	static constexpr auto right( rect_type const& rect )noexcept {
		return access_traits::right( rect );
	}
	static constexpr auto bottom( rect_type const& rect )noexcept {
		return access_traits::bottom( rect );
	}

	static constexpr auto width( rect_type const& rect )noexcept {
		return access_traits::width( rect );
	}
	static constexpr auto height( rect_type const& rect )noexcept {
		return access_traits::height( rect );
	}
	template<typename Vec2AccessTraits>
	static constexpr auto center( rect_type const& rect )noexcept {
		using vector_type = typename Vec2AccessTraits::vector_type;
		return vector_type{
			std::midpoint( left( rect ), right( rect ) ),
			std::midpoint( top( rect ), bottom( rect ) )
		};
	}
	static constexpr bool intersects( rect_type const& lhs, rect_type const& rhs )noexcept {
		return
			( left( lhs ) < right( rhs ) && right( lhs ) > left( rhs ) ) &&
			( top( lhs ) < bottom( rhs ) && bottom( lhs ) > top( rhs ) );
	}
	static constexpr bool contains( rect_type const& lhs, rect_type const& rhs )noexcept {
		return
			( left( lhs ) < left( rhs ) && right( lhs ) > right( rhs ) ) &&
			( top( lhs ) < top( rhs ) && bottom( lhs ) > bottom( rhs ) );
	}
	template<typename Vec2AccessTraits>
	static constexpr bool contains( rect_type const& lhs, typename Vec2AccessTraits::vector_type const& rhs )noexcept {
		return
			( Vec2AccessTraits::x( rhs ) >= left( lhs ) && Vec2AccessTraits::x( rhs ) < right( lhs ) ) &&
			( Vec2AccessTraits::y( rhs ) >= top( lhs ) && Vec2AccessTraits::y( rhs ) < bottom( lhs ) );
	}
};
What the hell is this stuff?

Well, while messing around with some stuff throughout the years, I've run into cases where different libraries use different Rectangle and Vector2 type classes/structs. Wouldn't it be nice to be able to use a single interface and not have to reinvent the wheel? I mean, that's what templates are for right?

These "traits" classes aren't very useful unless you plan on writing some code where you want some flexibility in allowing the users of your code to be able to supply their own Vector2 or Rectangle classes.

There is a bit of prep work to be had by the user to make this work though. Each "traits" class requires an AccessTraits struct. This should be provided by the user to describe how to access the members and how to construct a 2D vector or rectangle. Most of the time, a 2D vector will most certainly have a constructor accepting two values for X and Y, but some people may like to just have an aggregate type ( think no constructors ). In these cases, a simple {xValue, yValue} would work, so the vector2_access_traits::construct may not be necessary.

Now let's think of all the ways a rectangle can be represented:
boundaries ( left, top, right, bottom )
position and size
min and max points ( Vec2, Vec2 )

The rect_traits::construct passes left, right, top, bottom to the user provided rect_member_access_traits::construct in the same order. It is up to the user to create and return a rectangle using that data.

So, an implementation where the rectangle is represented as position, width, height would need to create their construct function like:

Code: Select all

template<> struct rect_member_access_traits<Rect<float>>{
    using rect_type = Rect<float>;
    using scalar_type = float;
    static constexpr auto construct( float left_, float top_, float right_, float bottom_ )noexcept{
        // first parameter is the position of the left top corner
        // second parameter is the width
        // third parameter is the height
        return rect_type{ { left_, top_ }, right - left, bottom - top };
    }
};
Or, how about the chili Rect where the constructor parameters are ( left, right, top, bottom )

Code: Select all

template<> struct rect_member_access_traits<Rect<float>>{
    using rect_type = Rect<float>;
    using scalar_type = float;
    static constexpr auto construct( float left_, float top_, float right_, float bottom_ )noexcept{
        return rect_type{ left_, right, top, bottom };
    }
};
Another reason for having the member access traits struct would also be in case the user has their members private so would need to be accessed from accessor functions

Code: Select all

template<> struct rect_member_access_traits<Rect<float>>{
    using rect_type = Rect<float>;
    using scalar_type = float;
    
    static constexpr auto left( rect_type const& rect )noexcept{
        return rect.GetLeft();
    }
    static constexpr void( left( rect_type& rect, scalar_type value )noexcept{
        rect.SetLeft( value );
    }
};
As you can see, there are too many ways to interact with these types of data types, so some user information is needed.

As to how this would be useful, well, I am writing a templated quadtree and needed some bounding box type. I didn't want to force the use of yet another Rect type, so I got off on another tangent and created this. This way I can use a single interface and also allow users to use whatever rectangle class they have on hand. For you all it would be chili's Rect<T> class, or maybe an sf::FloatRect or D2D1_RECT_F if you use Direct2D.

Another thing I think would be cool is if it made it into something like Box2D or another library thus avoiding users having to mix between the different Vector2 types ( b2Vec2, Vec2, D2D1_POINT_F, sf::Vector2, etc... ).

Anyway, just thought I'd share.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

albinopapa
Posts: 4373
Joined: February 28th, 2013, 3:23 am
Location: Oklahoma, United States

Re: Rect and Vec2 traits

Post by albinopapa » August 10th, 2020, 6:38 am

Oh, almost forgot, here's a sample of how to create the vector2_member_access_traits using the Vec2 from chili's framework.

Code: Select all

template<> struct vector2_member_access_traits<Vec2> {
	using vector_type = Vec2;
	using scalar_type = float;

	static constexpr auto construct( scalar_type x_, scalar_type y_ )noexcept {
		return vector_type{ x_, y_ };
	}
	static constexpr auto x( vector_type const& vec )noexcept {
		return vec.x;
	}
	static constexpr auto y( vector_type const& vec )noexcept {
		return vec.y;
	}

	static constexpr void x( vector_type& vec, scalar_type value )noexcept {
		vec.x = value;
	}
	static constexpr void y( vector_type& vec, scalar_type value )noexcept {
		vec.y = value;
	}
};
And the RectF from a version of chili's framework

Code: Select all

template<> struct rect_member_access_traits<RectF> {
	using rect_type = RectF;
	using scalar_type = float;

	static constexpr auto construct( scalar_type left_, scalar_type top_, scalar_type right_, scalar_type bottom_ )noexcept {
		return rect_type{ left_, right_, top_, bottom_ };
	}
	static constexpr auto left( rect_type const& rect )noexcept {
		return rect.left;
	}
	static constexpr auto top( rect_type const& rect )noexcept {
		return rect.top;
	}
	static constexpr auto right( rect_type const& rect )noexcept {
		return rect.right;
	}
	static constexpr auto bottom( rect_type const& rect )noexcept {
		return rect.bottom;
	}
	static constexpr auto width( rect_type const& rect )noexcept {
		return right( rect ) - left( rect );
	}
	static constexpr auto height( rect_type const& rect )noexcept {
		return bottom( rect ) - top( rect );
	}
};
The reason width() and height() are here is in instances where a rectangle is represented as position and size, I want to be able to just get the width directly instead of calculating it since it is directly available.
If you think paging some data from disk into RAM is slow, try paging it into a simian cerebrum over a pair of optical nerves. - gameprogrammingpatterns.com

Post Reply