Create a class that can return a glm::vec2 or a glm::vec3


#1

Hello, I’m creating some classes that can be used in both 2D and 3D context.
I would like to have one constructor that depending on the arguments, will returns a glm::vec3 or a glm::vec2. For example:

auto ray = Ray(glm::vec3(100, 200,50), glm::vec3(0.,1., 0.));
ray.getDirection(); //this should return a glm::vec3

auto ray = Ray(glm::vec2(10,30), glm::vec2(0.,1.));
ray.getDirection(); //this should return a glm::vec2

For now, I have created two different constructors, one for the 3D ray, and another one for the 2D ray. I have now to implement the getDirection method that checks if it is a 2D ray or a 3D ray and returns a glm::vec3 or a glm::vec2. But I do not like this approach, I see that those checks can go wild in my code and I have the feeling that I should separate the two classes.
At the same time, the code is very similar between the two implementations.

Any suggestion about how I should proceed? is the use of templates the way to go?


#2

yes you should use templates:

template<class T>
class Ray{
public:
    Ray(const T& from, const T& to);
    T getDirection();
...

#3

And what if a method make sense only on a 2D ray? like, for example a ray that intersect an ofPolyline?


#4

the syntax is kind of terrible but you can use enable_if http://en.cppreference.com/w/cpp/types/enable_if

I think you should be able to do something like: (haven’t tested it so it might not be 100% correct):

template<std::enable_if<std::is_equal<T::length_type, 2>>
void func() {}

since length_type returns the dimension in glm. and you could do the same with DIM if you wanted to make it compatible with ofVec as well. I think you can even use an or in the template.

In case a function is different in 3d or 2d then you can just specialize it by using the type, as in:

something like (after the declaration of the class or even on a cpp)

template<>
void Ray<glm::vec2>::func(){
....
}


template<>
void Ray<glm::vec3>::func(){
....
}

#5

I don’t have the code here right now. But I remember to face something similar, I needed differents draws and I put each type into a wrapper class. In your case a class for the glm::vec3 type and another for glm::vec2.
After that I used the concept of interface to implement each other draw methods.


#6

Thank you Arturo, it worked. I had to move the declaration of the specific method in a .cpp file, because it was giving me an error if the method was in the header.


#7

I still have 2 problems:

  • the first one is that when I declare a Ray instance I have to specify the type (and, at this point, I wonder if it is not better to use simple inheritance)
  • the second one is that the 2D specific methods are available also for a 3D class instance, although not implemented. This is my code
#pragma once
#include "ofMain.h"

namespace ofxraycaster {

template<class T>
class Ray {
public:
    Ray(){};
    Ray(T _origin, T _direction){
        origin = _origin;
        direction = glm::normalize(_direction);
    };

    void setup(T _origin, T _direction){
        origin = _origin;
        direction = glm::normalize(_direction);
    };

    // generic methods

    T getOrigin() const {
        return origin;
    };

    void setOrigin(T _origin){
        origin = _origin;
    };

    T getDirection() const {
        return direction;
    };

    void setDirection(T _direction){
        direction = _direction;
    };

    // Returns the linear-interpolation from the ray origin along its direction vector where t is in the range from 0 to infinite.
    T lerp(const float t) const {
        auto result = direction;
        result *= t;
        result += origin;
        return result;
    }

    void draw(float radius = 20.){
        ofPushStyle();
        // draw origin
        ofSetColor(255, 0, 0);
        ofDrawSphere(origin.x, origin.y, radius);

        // draw direction
        ofSetColor(0,0,255);
        auto end = origin + (direction * (radius*4.));
        ofSetLineWidth(3);
        ofDrawLine(origin,end);
        ofSetLineWidth(1);
        ofPopStyle();
    };

    // 2D specific methods
    void intersectsSegment(glm::vec2 a, glm::vec2 b, glm::vec2& intersection, bool& intersects);
    void intersectsPolyline(const ofPolyline& poly, glm::vec2& intersection, bool& intersects);
    // 3D specific methods
    // TODO intersectSphere

private:
    T origin;
    T direction;
};

}// end namespace

This is the Ray.cpp class, where i put the template specializations:

#include "Ray.h"

// 2D methods
template<>
void ofxraycaster::Ray<glm::vec2>::intersectsSegment(glm::vec2 a, glm::vec2 b, glm::vec2& intersection, bool& intersects){
    //implementation
};

template<>
void ofxraycaster::Ray<glm::vec2>::intersectsPolyline(const ofPolyline& poly, glm::vec2& intersection, bool& intersects){
    //implementation
};

And whenever I need to declare an instance variable for a 2D ray I use:

ofxraytracer::Ray<glm::vec2> ray

I’m interested using templates in this case because I think it is the best solution for this situation where there are a lot of things in common between a 2D and a 3D ray, and some small things that does not make any sense in 2D world, and viceversa.


#8

I’m still fighting against this enable_if. I want to use it to make a method available just if my ray class is a 2D ray. This is my code:

namespace ofxraycaster {

template<class T>
class Ray {
public:
    Ray(){};
    Ray(T _origin, T _direction){
        origin = _origin;
        direction = glm::normalize(_direction);
    };

    // generic methods

    T getOrigin() const {
        return origin;
    };

    T getDirection() const {
        return direction;
    };

    void draw();

    // 2D specific methods
    void intersectsSegment(glm::vec2 a, glm::vec2 b, glm::vec2& intersection, bool& intersects);

private:
    T origin;
    T direction;
};

}// end namespace

I have tried the following:

namespace ofxraycaster {

template<class T>
class Ray {
public:
    Ray(){};
    /// ...
    template<typename std::enable_if<sizeof(T)==2>::type>
    void intersectsSegment(glm::vec2 a, glm::vec2 b, glm::vec2& intersection, bool& intersects);
};
}// end namespace

And I receive this error

If I do as @arturo was suggesting, checking the size of the vector:

template<typename std::enable_if<std::equal_to<T::length_type, 2>>
void intersectsSegment(glm::vec2 a, glm::vec2 b, glm::vec2& intersection, bool& intersects);

i get this other error:

I’m following this tutorial:
https://eli.thegreenplace.net/2014/sfinae-and-enable_if/, in particular the example:

template <class T>
typename std::enable_if<std::is_arithmetic<T>, bool>::type
signbit(T x)
{
    // implementation
}

But I can get not it working, any hint is appreciated


#9

sizeof(2) is incorrect cause it should be sizeof(2) * sizeof(float)but i don’t think relying on sizeof is a good idea.

what i was refering to is:

glm::vec2::components, not length_type but it’s gone in the latest glm version that we are using now.

what seems to work is:


    template<typename = typename std::enable_if<std::is_same<glm::vec2, T>::value || std::is_same<ofVec2f, T>::value>::type>
	void intersectsSegment(){
		std::cout << 2 << std::endl;
	}

which will work for both glm::vec2 and ofVec2f


#10

It gives me this error:

This is the header file:

#pragma once
#include "ofMain.h"
#include "Plane.h"

namespace ofxraycaster {

template<class T>
class Ray {
public:
    Ray(){};
    Ray(T _origin, T _direction){
        origin = _origin;
        direction = glm::normalize(_direction);
    };

    void setup(T _origin, T _direction){
        origin = _origin;
        direction = glm::normalize(_direction);
    };

    // generic methods

    T getOrigin() const {
        return origin;
    };

    void setOrigin(T _origin){
        origin = _origin;
    };

    T getDirection() const {
        return direction;
    };

    void setDirection(T _direction){
        direction = _direction;
    };

    void draw(float radius = 20.);

    // Returns the linear-interpolation from the ray origin along its direction vector where t is in the range from 0 to infinite.
    T lerp(const float t) const {
        auto result = direction;
        result *= t;
        result += origin;
        return result;
    }


    // 2D specific methods
    template<typename = typename std::enable_if<std::is_same<glm::vec2, T>::value || std::is_same<ofVec2f, T>::value>::type>
    void intersectsSegment(glm::vec2 a, glm::vec2 b, glm::vec2& intersection, bool& intersects);

    void intersectsSegmentDistance(glm::vec2 a, glm::vec2 b, float& distance, bool& intersects);

    void intersectsPolyline(const ofPolyline& poly, glm::vec2& intersection, glm::vec2& surfaceNormal, bool& intersects);

private:
    T origin;
    T direction;
};

}// end namespace

And this the cpp one

#include "Ray.h"

// 2D methods
template<>
void ofxraycaster::Ray<glm::vec2>::intersectsSegmentDistance(glm::vec2 a, glm::vec2 b, float& distance, bool& intersects){
    intersects = false;

    float x = origin.x;
    float y = origin.y;
    float dx = direction.x;
    float dy = direction.y;

    float s, denom;
    if (dy / dx != (b.y - a.y) / (b.x - a.x)){
        denom = ((dx * (b.y - a.y)) - dy * (b.x - a.x));
        if (denom != 0) {
            distance = (((y - a.y) * (b.x - a.x)) - (x - a.x) * (b.y - a.y)) / denom;
            s = (((y - a.y) * dx) - (x - a.x) * dy) / denom;
            if (distance >= 0 && s >= 0 && s <= 1) {
                intersects = true;
                //return { x: x + r * dx, y: y + r * dy };
            }
        }
    }
    
};

template<>
// https://gamedev.stackexchange.com/questions/109420/ray-segment-intersection
void ofxraycaster::Ray<glm::vec2>::intersectsSegment(glm::vec2 a, glm::vec2 b, glm::vec2& intersection, bool& intersects){
    intersects = false;

    float distance = 0;
    intersectsSegmentDistance(a, b, distance, intersects);
    if (intersects) {
        intersection.x = origin.x + distance * direction.x;
        intersection.y = origin.y + distance * direction.y;
    }
};



template<>
void ofxraycaster::Ray<glm::vec2>::intersectsPolyline(const ofPolyline& poly, glm::vec2& intersection, glm::vec2& surfaceNormal, bool& intersects){

    vector<glm::vec3> container = poly.getVertices();
    float distance = std::numeric_limits<float>::infinity();
    for (int i = 0; i < container.size(); i++) {

        // qui in realta' dovresti chiudere solo se la poly e' closed.
        auto endSegmentIndex = container.size() == (i + 1) ? 0 : i + 1;

        float tmpDistance = distance;
        bool tmpIntersect = false;
        intersectsSegmentDistance(container[i], container[endSegmentIndex],
                                  tmpDistance, tmpIntersect);
        if (tmpIntersect) {
            if (intersects == false) { intersects = true; };
            if (tmpDistance < distance) {
                // we first find the direction of the segment.
                glm::vec2 segmentDir = glm::normalize(container[i] - container[endSegmentIndex]);
                // and then we use as normal a vector orthogonal to direction.
                surfaceNormal = glm::vec2(segmentDir.y, -segmentDir.x);
                distance = tmpDistance;
            }
        }

    }

    if (intersects) {
        intersection.x = origin.x + distance * direction.x;
        intersection.y = origin.y + distance * direction.y;
    }
};

template<>
void ofxraycaster::Ray<glm::vec2>::draw(float radius){
    ofPushStyle();
    // draw origin
    ofSetColor(255, 0, 0);
    ofDrawCircle(origin, radius);

    // draw direction
    ofSetColor(0,0,255);
    auto end = origin + (direction * (radius*4.));
    ofSetLineWidth(3);
    ofDrawLine(origin,end);
    ofSetLineWidth(1);
    ofPopStyle();
};

/////////////////////////3D Methods ////////////
template<>
void ofxraycaster::Ray<glm::vec3>::draw(float radius){
    ofPushStyle();
    // draw origin
    ofSetColor(255, 0, 0);
    ofDrawSphere(origin, radius);

    // draw direction
    ofSetColor(0,0,255);
    auto end = origin + (direction * (radius*4.));
    ofSetLineWidth(3);
    ofDrawLine(origin,end);
    ofSetLineWidth(1);
    ofPopStyle();
};

I assuming that the enable_if goes into the header file, but maybe I am wrong.


#11

that works for me except that i need the implementation directly in the .h, what platform are you in?


#12

I’m on mac. I have tried to remove the method from the .cpp file and to write the implementation directly in the header but I have the same error.

I have found this https://stackoverflow.com/questions/13964447/why-compile-error-with-enable-if and I think it is related, but I still do not have a solution.


#13

might be that this doesn’t work on clang the same as in linux, i’ve found that syntax that compiles in linux won’t on vs or clang with this kind of templated code


#14

Hi @edapx
the following compiles on my mac, running sierra and xcode 8.3.2.

	template<class V = T>
		typename std::enable_if<std::is_same<glm::vec2, V>::value || std::is_same<ofVec2f, V>::value, void>::type
///		template<typename = typename std::enable_if<std::is_same<glm::vec2, T>::value || std::is_same<ofVec2f, T>::value>::type>
		intersectsSegment(glm::vec2 a, glm::vec2 b, glm::vec2& intersection, bool& intersects){
		// https://gamedev.stackexchange.com/questions/109420/ray-segment-intersection
			intersects = false;
			
			float distance = 0;
			intersectsSegmentDistance(a, b, distance, intersects);
			if (intersects) {
				intersection.x = origin.x + distance * direction.x;
				intersection.y = origin.y + distance * direction.y;
			}
		}

It should go in the h file though.
hope this helps.
best


#15

It worked :wink: thank you @roymacdonald and @arturo for your help.

(and for showing me one of the weirdest and ugliest c++ tricks ever, I really hope this is the first and last time that use this enable_if !)


#16

:slight_smile:
I had to deal with this thing recenlty. Yes, it is really ugly, but the worst is that you need to have the implementation in the h file!
cheers!