```{-# LANGUAGE Rank2Types, TypeOperators, FlexibleContexts #-}
{-# OPTIONS_GHC -Wall #-}
----------------------------------------------------------------------
-- |
-- Module      :  Graphics.FieldTrip.ParamSurf
-- Copyright   :  (c) Conal Elliott 2008
--
-- Maintainer  :  conal@conal.net
-- Stability   :  experimental
--
-- Parametric surfaces with automatic normals
----------------------------------------------------------------------

module Graphics.FieldTrip.ParamSurf where

import Control.Applicative

import Data.NumInstances ()
import Data.VectorSpace
import Data.Cross

type HeightField s = Two s -> One s
type Surf        s = Two s -> Three s

type USurf = forall s. Floating s => Surf s

type Curve2 s = One s -> Two s
type Curve3 s = One s -> Three s

type Warp1 s = One   s -> One   s
type Warp2 s = Two   s -> Two   s
type Warp3 s = Three s -> Three s

mul2pi :: Floating s => s -> s
mul2pi = (* (2*pi))

-- | Trig functions with unit period ([-1/2,1/2])
cosU, sinU :: Floating s => s -> s
cosU = cos . mul2pi
sinU = sin . mul2pi

-- | Turn a height field into a surface
hfSurf :: HeightField s -> Surf s
hfSurf field = \ (u,v) -> (u, v, field (u,v))

-- | Like 'hfSurf' but for curve construction
fcurve :: Warp1 s -> Curve2 s
fcurve f = \ u -> (u, f u)

-- | Unit circle.
circle :: Floating s => Curve2 s
circle = liftA2 (,) cosU sinU

-- | Half semi circle, with theta in [-pi/2,pi/2]
semiCircle :: Floating s => Curve2 s
semiCircle = circle . (/ 2)

-- | Torus, given radius of sweep circle and cross section
torus :: (Floating s, VectorSpace s s) => s -> s -> Surf s
-- torus sr cr = revolve (\ s -> (sr,0) ^+^ cr *^ circle s)
torus sr cr = revolve (const (sr,0) ^+^ cr *^ circle)

-- Surface of revolution, formed by rotation around Z axis.  The curve is
-- parameterized by u, and the rotation by v.  In this generalized
-- version, we have not a single curve, but a function from v to curves.
revolveG :: Floating s => (s -> Curve2 s) -> Surf s
revolveG curveF = \ (u,v) -> onXY (rotate (-2*pi*v)) (addY (curveF v) u)

revolve :: Floating s => Curve2 s -> Surf s
revolve curve = revolveG (const curve)

-- A sphere is a revolved semi-circle
sphere1 :: Floating s => Surf s
sphere1 = revolve semiCircle

-- | Profile product.
profile :: Num s => Curve2 s -> Curve2 s -> Surf s
profile curve prof (u,v) = (cx*px,cy*px,py)
where
(cx,cy) = curve u
(px,py) = prof  v

-- More spheres
sphere2,sphere3 :: Floating s => Surf s
sphere2 = profile circle semiCircle
sphere3 = profile semiCircle circle

-- | Frustrum, given base & cap radii and height.
frustrum :: (Floating s, VectorSpace s s) => s -> s -> s -> Surf s
frustrum baseR topR h = profile circle rad
where
rad t = (lerp baseR topR (t + 1/2), h*t)

-- | Unit cylinder.  Unit height and radii
ucylinder :: (Floating s, VectorSpace s s) => Surf s
ucylinder = profile circle (const 1)

-- | Given a combining op and two curves, make a surface.  A sort of
-- Cartesian product with combination.
cartF :: (a -> b -> c) -> (u -> a) -> (v -> b) -> ((u,v) -> c)
cartF op f g = \ (u,v) -> f u `op` g v

-- Sweep a basis curve by a sweep curve.  Warning: does not reorient the
-- basis curve as cross-section.  TODO: Frenet frame.
sweep :: VectorSpace s s' => Curve3 s -> Curve3 s -> Surf s
sweep = cartF (^+^)

-- | One period, unit height eggcrate
eggcrateH :: Floating s => HeightField s
eggcrateH = cartF (*) cosU sinU

revolveH :: (Floating s, InnerSpace s s) => Warp1 s -> HeightField s
revolveH = (. magnitude)

rippleH :: (Floating s, InnerSpace s s) => HeightField s
rippleH = revolveH sinU

-- | Simple ripply pond shape
ripple :: Floating s => Surf s
ripple = -- onXY' (2 *^) \$
revolve (const (0.5,0) - fcurve sinU)

-- | Apply a displacement map at a value

displaceV :: (InnerSpace v s, Floating s, HasNormal v) =>
v -> s -> v
displaceV v s = v ^+^ s *^ normal v

-- | Apply a displacement map to a function (e.g., 'Curve2' or 'Surf') or
-- other container.
displace :: (InnerSpace v s, Floating s, HasNormal v, Applicative f) =>
f v -> f s -> f v
displace = liftA2 displaceV

---- Misc

rotate :: Floating s => s -> Warp2 s
rotate theta = \ (x,y) -> (x * c - y * s, y * c + x * s)
where c = cos theta
s = sin theta

addX = fmap (\ (y,z) -> (0,y,z))
addY = fmap (\ (x,z) -> (x,0,z))
addZ = fmap (\ (x,y) -> (x,y,0))

addYZ = fmap (\ x -> (x,0,0))
addXZ = fmap (\ y -> (0,y,0))
addXY = fmap (\ z -> (0,0,z))

onX,onY,onZ :: Warp1 s -> Warp3 s
onX f (x,y,z) = (f x, y, z)
onY f (x,y,z) = (x, f y, z)
onZ f (x,y,z) = (x, y, f z)

onXY,onYZ,onXZ :: Warp2 s -> Warp3 s
onXY f (x,y,z) = (x',y',z ) where (x',y') = f (x,y)
onXZ f (x,y,z) = (x',y ,z') where (x',z') = f (x,z)
onYZ f (x,y,z) = (x ,y',z') where (y',z') = f (y,z)

onX',onY',onZ' :: Warp1 s -> (a -> Three s) -> (a -> Three s)
onX' = fmap fmap onX
onY' = fmap fmap onY
onZ' = fmap fmap onZ

onXY',onXZ',onYZ' :: Warp2 s -> (a -> Three s) -> (a -> Three s)
onXY' = fmap fmap onXY
onXZ' = fmap fmap onXZ
onYZ' = fmap fmap onYZ
```