Fix homogeneous coordinates (and the affine transformations on them)
- Dominant language
- Haskell
- Stars
- 220
- Forks
- 55
- PR merge metrics
- No merged PRs in 30d
Description
I'm a little bit confused about how `Affine` is supposed to work in the linear package.
In particular, affine matrix transformations. The api encourages you to create a 4x4 matrix for your affine transformation:
```haskell
mkTransformation :: Num a => Quaternion a -> V3 a -> M44 a
mkTransformationMat :: Num a => M33 a -> V3 a -> M44 a
```
The `M44` result presumably needs to be multiplied with a `V4` representation in [homogeneous coordinates](https://en.wikipedia.org/wiki/Homogeneous_coordinates#Use_in_computer_graphics). This is pretty standard fare in computer graphics libraries:
```haskell
let affineMat = mkTransformationMat rotationScale translation :: M44 Double
homogeneousVec = V4 x y z 1 :: V4 Double
in affineMat !* homogeneousVec
```
Another convention that I believe is reasonably standard in computer graphics packages is for `Point` to represent homogeneous coordinates (since points can be translated and projected while vectors typically represent a scale and direction only). `Linear.Affine` exposes a `Point` type which presumably serves this function, but...
## 1. `Point` versus `point`
In linear `Point` is just a newtype over the underlying vector type. Confusingly neither `point` nor `normalizePoint` has anything to do with `Point`:
```haskell
point :: Num a => V3 a -> V4 a
normalizePoint :: Fractional a => V4 a -> V3 a
```
## 2. Affine transformations on points
Furthermore, I can't quite make out how one would go about multiplying the before mentioned `M44` transformation matrix with my `Point`:
```haskell
let affineMat = mkTransformationMat rotationScale translation :: M44 Double
homogeneousVec = P (V3 x y z) :: Point V3 Double
in affineMat !* homogeneousVec -- Couldn't match type ‘Point f’ with ‘V3’
-- Expected type: V3 Double
-- Actual type: Point f Double
-- In the second argument of ‘(!*)’, namely ‘P v’
```
In addition, it is also sometimes useful to use a non-square matrix when there is no perspective projection/shear present in the transform. This is especially true with 2D graphics where most transformations are rotation/scale/translation, conveniently representable as `M23`:
```haskell
let scaleAndTranslate = V2
(V3 sx 0 tx)
(V3 0 sy ty)
homogeneousVec = P (V2 x y) -- Alternatively: V3 x y 1
in affineMat !* homogeneousVec -- Couldn't match type ‘Point f’ with ‘V2’
```
How should I deal with my shear / translate / perspective transformations?
There's the temptation to add conversion functions:
```haskell
homogeneous2 :: Point V2 Double -> V3 Double
homogeneous2 (P (V2 x y)) = V3 x y 1
homogeneous3 :: Point V3 Double -> V4 Double
homogeneous3 (P (V3 x y z)) = V4 x y z 1
```
But this is far from ideal: if you started out with `Point` then you'd expect to have a `Point` result after applying a transformation to it.
## What to do?
In conclusion, looking at this from the application programmer side, the `Point` type looks useless since you inevitably need to drop it off of your type to do anything useful with them.
I'm not sure what the best approach would be to fixing this would be or whether I've misunderstood the intended interface for `Linear.Affine`: E.g. `Affine` is nice, but I'm used to using matrix multiplication in order to compose long chains of transformations. Furthermore, shear and perspective projection is not available, which is weird considering [wikipedia's description](https://en.wikipedia.org/wiki/Affine_transformation#Augmented_matrix):
> The above-mentioned augmented matrix is called *affine transformation matrix*, or *projective transformation matrix* (as it can also be used to perform projective transformations).
Thanks for any help!
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.