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.