Ncollide2d/3d polymorphism

TLDR

Is it possible to write code that is generic over ncollide2d and ncollide3d?

Details

I have prototyped some code using ncollide2d. I’m really more interested in the 3-dimensional version, but the 2-d version is useful for exploration purposes, so I would like to keep it around. The 2-D and 3-D versions should be almost identical in many respects, so I’d rather not duplicate the code but write a generic version wherever possible.

The original code starts off with something like this:

use ncollide2d as nc;

type Length = f64;
type Vector = nc::math ::Vector<Length>;
type Point  = nc::math ::Point <Length>;
type Cuboid = nc::shape::Cuboid<Length>;
type Ray    = nc::query::Ray   <Length>;
// etc.

which allows me to switch most of the code over from 2-D to 3-D simply by changing the use at the top from ncollide2d to ncollide3d.

However, I would like to be able to keep both the 2-D and 3-D versions at the same time. I tried the following approach

trait Dimension {
    type Vector;
    type Point;
    // etc.
}

struct D2;
struct D3;

use ncollide2d as nc2;
use ncollide3d as nc3;

impl Dimension for D2 {
    type Vector = nc2::math::Vector<Length>;
    type Point  = nc2::math::Point <Length>;
    // etc.
}

impl Dimension for D3 {
    type Vector = nc3::math::Vector<Length>;
    type Point  = nc3::math::Point <Length>;
    // etc.
}

with the aim of parametrizing the code on the Dimension trait.

This fails because many of the methods which are available in both 2-D and 3-D are not part any trait that I can identify, so the compiler is unaware of their availability.

Take 2

I also tried to bypass the ncollideNd type aliases and go directly to the underlying types hidden behind them, that is, for example, nalgebra::geometry::Point<Length, D> rather than ncollide2d::math::Point<Length>, with the aim of parametrizing on D: DimName.

This also failed, for reasons which are probably too messy to be usefully discussed at this point.

The question

Is it possible to write such polymorphic code, or am I fundamentally misguided?

I think writing polymorphic code would be very difficult. You would basically need to define your own trait for each geometric operation you want to use from ncollide.

In nphysics and Rapier, I do something similar to what you have been doing your your type aliases. But of course, this means I don’t have the 2D and 3D version into a single crate. One alternative would be to define three crates.

# crate1/cargo.toml
[package]
name = "my_crate2D"

[features]
default = [ "dim2" ]
dim2 = [ ]

[lib]
name = "my_crate2D"
path = "../src/lib.rs"

[dependencies]
ncollide2d = "0.24"
# crate2/cargo.toml
[package]
name = "my_crate3D"

[features]
default = [ "dim3" ]
dim3 = [ ]

[lib]
name = "my_crate3D"
path = "../src/lib.rs"

[dependencies]
ncollide3d = "0.24"
// src/lib.rs
#[cfg(feature = "dim2")]
use ncollide2d as nc;
#[cfg(feature = "dim3")]
use ncollide3d as nc;

// etc.

The trick with the dim2, dim3 features is to make it possible for the 2D and 3D crates to share the same code files rooted at src/lib.rs.
And then you have a third crate that re-export these two:

# crate3/cargo.toml
[package]
name = "my_crate2D3D"

[dependencies]
my_crate2D = "*"
my_crate3D = "*"
# crate3/src/lib.rs
pub use my_crate2D;
pub use my_crate3D;

So in the end you have both 2D and 3D exposed by the third crate. Though of course this does not fully replace polymorphism.