rsagl-math-0.6.0.0: RSAGL/Math/Curve.hs
{-# LANGUAGE GeneralizedNewtypeDeriving, ExistentialQuantification, Rank2Types #-}
module RSAGL.Math.Curve
(Curve,
zipCurve,
iterateCurve,
transposeCurve,
curve,
Surface,
surface,
wrapSurface,
unwrapSurface,
transposeSurface,
zipSurface,
iterateSurface,
halfIterateSurface,
flipTransposeSurface,
translateCurve,
scaleCurve,
clampCurve,
loopCurve,
controlCurve,
transformCurve2,
uv_identity,
translateSurface,
scaleSurface,
transformSurface,
transformSurface2,
surfaceDerivative,
curveDerivative,
orientationLoops,
newellCurve,
surfaceNormals3D,
SamplingAlgorithm,
IntervalSample,
intervalRange,
intervalSize,
intervalSample,
intervalSingleIntegral,
linearSamples,
adaptiveMagnitudeSamples,
integrateCurve)
where
import RSAGL.Math.ListUtils
import RSAGL.Math.Vector
import RSAGL.Math.Angle
import RSAGL.Math.Affine
import Data.List
import Data.Maybe
import Control.Parallel.Strategies
import Control.Applicative
import RSAGL.Math.AbstractVector
import Debug.Trace
import RSAGL.Math.Interpolation
import RSAGL.Math.FMod
import RSAGL.Math.Types
import RSAGL.Math.BoundingBox
-- | A parametric function that is aware of it's own sampling interval. The first parameter is the sampling interval, while the second is the curve input parameter.
type CurveF a = (RSdouble,RSdouble) -> a
-- | A surface is a curve of curves.
type SurfaceF a = CurveF (CurveF a)
-- | A 'Curve' is a parametric function from a one-dimensional space into a space of an arbitrary datatype. The key feature of a 'Curve' is that it is aware of it's own
-- sampling interval. Using this information and appropriate arithmetic and scalar multiplication functions provided by RSAGL.AbstractVector, a 'Curve' can be differentiated or integrated.
newtype Curve a = Curve { fromCurve :: CurveF a }
instance Functor Curve where
fmap g (Curve f) = Curve $ g . f
instance Applicative Curve where
pure a = Curve $ const a
f <*> a = zipCurve ($) f a
instance (AffineTransformable a) => AffineTransformable (Curve a) where
scale v = fmap (scale v)
translate v = fmap (translate v)
rotate vector angle = fmap (rotate vector angle)
transform m = fmap (transform m)
rotateX angle = fmap (rotateX angle)
rotateY angle = fmap (rotateY angle)
rotateZ angle = fmap (rotateZ angle)
instance NFData (Curve a) where
rnf (Curve f) = seq f ()
-- | Sample a specific point on a curve given the sampling interval (first parameter) and the point on the curve (second parameter).
sampleCurve :: Curve a -> RSdouble -> RSdouble -> a
sampleCurve (Curve f) = curry f
-- | Sample a curve at regular intervals in the range 0..1 inclusive.
iterateCurve :: Integer -> Curve x -> [x]
iterateCurve n c = map f $ zeroToOne n
where f = sampleCurve c (0.25/fromInteger n)
-- | Combine two curves using an arbitrary function.
zipCurve :: (x -> y -> z) -> Curve x -> Curve y -> Curve z
zipCurve f (Curve x) (Curve y) = Curve $ \hu -> f (x hu) (y hu)
-- | Arbitrarily transform a 'Curve'.
mapCurve :: (CurveF a -> CurveF a) -> Curve a -> Curve a
mapCurve f = Curve . f . fromCurve
-- | Arbitrarily transform a surface 'Curve'.
mapCurve2 :: (SurfaceF a -> SurfaceF a) -> Curve (Curve a) -> Curve (Curve a)
mapCurve2 f = Curve . (Curve .) . f . (fromCurve .) . fromCurve
-- | Translate a curve along the axis of the input parameter.
translateCurve :: RSdouble -> Curve a -> Curve a
translateCurve x = mapCurve $ \f (h,u) -> f (h,u-x)
-- | Scale a curve along the axis of the input parameter. Factors greater than one have a "zoom in" effect, while
-- factors less than one have a "zoom out" effect.
scaleCurve :: RSdouble -> Curve a -> Curve a
scaleCurve s = mapCurve (\f (h,u) -> f (h/s,u/s))
-- | Clamp lower and upper bounds of a curve along the axis of the input parameter.
clampCurve :: (RSdouble,RSdouble) -> Curve a -> Curve a
clampCurve (a,b) | b < a = clampCurve (b,a)
clampCurve (a,b) = mapCurve $ \f (h,u) -> f (h,min b $ max a u)
-- | Loop a curve onto itself at the specified bounds.
loopCurve :: (RSdouble,RSdouble) -> Curve a -> Curve a
loopCurve (a,b) | b < a = loopCurve (b,a)
loopCurve (a,b) = mapCurve $ \f (h,u) -> f (h,(u-a) `fmod` (b-a) + a)
-- | Transform a curve by manipulating control points.
controlCurve :: (RSdouble,RSdouble) -> (RSdouble,RSdouble) -> Curve a -> Curve a
controlCurve (u,v) (u',v') = translateCurve u' . scaleCurve ((u'-v') / (u-v)) . translateCurve (negate u)
-- | Transpose the inner and outer components of a curve.
transposeCurve :: Curve (Curve a) -> Curve (Curve a)
transposeCurve = mapCurve2 flip
-- | Lift two curve transformations onto each axis of a second order curve.
transformCurve2 :: (forall u. Curve u -> Curve u) -> (forall v. Curve v -> Curve v) -> Curve (Curve a) -> Curve (Curve a)
transformCurve2 fu fv = transposeCurve . fu . transposeCurve . fv
-- | Define a simple curve.
curve :: (RSdouble -> a) -> Curve a
curve = Curve . uncurry . const
-- | A 'Surface' is a based on a 'Curve' with an output of another 'Curve'.
newtype Surface a = Surface (Curve (Curve a)) deriving (NFData,AffineTransformable)
-- | Define a simple surface.
surface :: (RSdouble -> RSdouble -> a) -> Surface a
surface f = Surface $ curve (\u -> curve $ flip f u)
wrapSurface :: Curve (Curve a) -> Surface a
wrapSurface = Surface
unwrapSurface :: Surface a -> Curve (Curve a)
unwrapSurface (Surface s) = s
-- | Transpose the axes of a 'Surface'.
transposeSurface :: Surface a -> Surface a
transposeSurface (Surface s) = Surface $ transposeCurve s
-- | Sample a surface at regularly spaced lattice points in the range 0..1 inclusive.
iterateSurface :: (Integer,Integer) -> Surface a -> [[a]]
iterateSurface (u,v) (Surface s) = map (iterateCurve u) $ iterateCurve v s
-- | Sample the outer 'Curve' of a 'Surface' at regularly spaced intervals.
halfIterateSurface :: Integer -> Surface a -> [Curve a]
halfIterateSurface u = iterateCurve u . unwrapSurface
instance Functor Surface where
fmap f (Surface x) = Surface $ fmap (fmap f) x
instance Applicative Surface where
pure a = surface (const $ const a)
f <*> a = zipSurface ($) f a
-- | Combine two surfaces using an arbitrary function.
zipSurface :: (x -> y -> z) -> Surface x -> Surface y -> Surface z
zipSurface f (Surface x) (Surface y) = Surface $ zipCurve (zipCurve f) x y
-- | Lift a transformation on a second order 'Curve' onto a Surface.
transformSurface :: (Curve (Curve a) -> Curve (Curve a)) -> Surface a -> Surface a
transformSurface f = Surface . f . unwrapSurface
-- | Lift two curve transformations onto each axis of a Surface.
transformSurface2 :: (forall u. Curve u -> Curve u) -> (forall v. Curve v -> Curve v) -> Surface a -> Surface a
transformSurface2 fu fv = transformSurface (transformCurve2 fu fv)
-- | Translate a surface over each of its input parameter axes, as translateCurve.
translateSurface :: (RSdouble,RSdouble) -> Surface a -> Surface a
translateSurface (u,v) = transformSurface2 (translateCurve u) (translateCurve v)
-- | Scale a surface along each of its input parameter axes, as scaleCurve.
scaleSurface :: (RSdouble,RSdouble) -> Surface a -> Surface a
scaleSurface (u,v) = transformSurface2 (scaleCurve u) (scaleCurve v)
-- | Transpose a surface while flipping the inner curve, so that that orientable surfaces retain their original orientation.
flipTransposeSurface :: Surface a -> Surface a
flipTransposeSurface = transformSurface (mapCurve2 $ \f (hu,u) (hv,v) -> f (hu,u) (hv,1-v)) . transposeSurface
-- | An identity 'Surface'.
uv_identity :: Surface (RSdouble,RSdouble)
uv_identity = surface (curry id)
-- | Take the derivative of a 'Curve'.
curveDerivative :: (AbstractSubtract p v,AbstractScale v) => Curve p -> Curve v
curveDerivative (Curve f) = Curve $ \(h,u) -> scalarMultiply (recip $ 2 * h) $ f (h/2,u+h) `sub` f (h/2,u-h)
-- | Take the piecewise derivative of a 'Surface' along the inner and outer curves.
surfaceDerivative :: (AbstractSubtract p v,AbstractScale v) => Surface p -> Surface (v,v)
surfaceDerivative s = zipSurface (,) (curvewiseDerivative s) (transposeSurface $ curvewiseDerivative $ transposeSurface s)
where curvewiseDerivative (Surface t) = Surface $ fmap curveDerivative t
-- | Determine the orientation of a 'Surface' by passing very small circles centered on each sampled point as the parametric input.
--
-- A gotchya with this operation is that as much as 3/4ths of the orientation loop may lie outside of the 0..1 range that is normally
-- sampled. Depending on how the surface is constructed, this may produce unexpected results. The solution is to clamp the
-- the problematic parametric inputs at 0 and 1 using 'clampSurface'.
--
-- As a rule, do clamp longitudinal axes that come to a singularity at each end.
-- Do not clamp latitudinal axes that are connected at each end.
--
orientationLoops :: Surface p -> Surface (Curve p)
orientationLoops (Surface s) = Surface $ Curve $ \(uh,u) -> Curve $ \(vh,v) ->
curve $ \t -> f (uh/2,u + uh*(sine $ fromRotations t))
(vh/2,v + vh*(cosine $ fromRotations t))
where f = fromCurve . fromCurve s
-- | Try to determine the normal vector to a curve.
newellCurve :: Curve Point3D -> Maybe Vector3D
newellCurve c = newell $ iterateCurve 16 c
-- | Try to determine the normal vectors of a surface using orientation loops. This is usually slower but more successful than 'surfaceNormals3DByPartialDerivatives'.
-- This generate a warning message if it cannot determine the normal vector at a sampled point.
-- See also 'orientationLoops'.
surfaceNormals3DByOrientationLoops :: Surface Point3D -> Surface SurfaceVertex3D
surfaceNormals3DByOrientationLoops s = SurfaceVertex3D <$> s <*> ((\c -> errmsg c (newellCurve c)) <$> orientationLoops s)
where errmsg c = fromMaybe (trace ("surfaceNormals3DByOrientationLoops: zero normal gave up: " ++ show (iterateCurve 16 c)) (Vector3D 0 0 0))
-- | Try to determine the normal vectors of a surface using partial derivatives.
surfaceNormals3DByPartialDerivatives :: Surface Point3D -> Surface (Maybe Vector3D)
surfaceNormals3DByPartialDerivatives s = safeCrossProduct <$> surfaceDerivative s
where x = snd $ boundingCenterRadius $ boundingBox $ concat $ iterateSurface (8,8) s
safeCrossProduct (u_,v_) =
do u <- aLargeVector (x/100) u_
v <- aLargeVector (x/100) v_
return $ vectorNormalize $ crossProduct u v
-- | Try to determine the normal vectors of a surface using multiple techniques.
surfaceNormals3D :: Surface Point3D -> Surface SurfaceVertex3D
surfaceNormals3D s = (\p by_pd by_newell -> case by_pd of
Just v -> SurfaceVertex3D p v
Nothing -> by_newell) <$>
s <*> (surfaceNormals3DByPartialDerivatives s) <*> (surfaceNormals3DByOrientationLoops s)
-- | An interval of a curve, including the curve, lower and upper bounds of the interval, and an instantaneous sample value for that interval.
data IntervalSample a = IntervalSample (Curve a) RSdouble RSdouble a
intervalSample :: Curve a -> RSdouble -> RSdouble -> IntervalSample a
intervalSample c l h = IntervalSample c l h $ sampleCurve c ((abs $ l - h) / 2) ((l+h) / 2)
-- | Lower and upper bounds of an 'IntervalSample'.
intervalRange :: IntervalSample a -> (RSdouble,RSdouble)
intervalRange (IntervalSample _ l h _) = (l,h)
-- | Size of the range of an 'IntervalSample'.
intervalSize :: IntervalSample a -> RSdouble
intervalSize (IntervalSample _ l h _) = abs $ h - l
-- | Instantaneous sample value of an 'Interval'.
intervalValue :: IntervalSample a -> a
intervalValue (IntervalSample _ _ _ a) = a
-- | Integral of the sample value over the range of the 'IntervalSample'.
intervalSingleIntegral :: (AbstractScale a) => IntervalSample a -> a
intervalSingleIntegral x = scalarMultiply (intervalSize x) $ intervalValue x
-- | Split an interval into three equal parts.
splitInterval :: IntervalSample a -> [IntervalSample a]
splitInterval (IntervalSample c l h a) = [intervalSample c l l',IntervalSample c l' h' a,intervalSample c h' h]
where l' = lerp (1/3) (l,h)
h' = lerp (2/3) (l,h)
type SamplingAlgorithm a = Curve a -> [IntervalSample a]
-- | Definite integral of a curve.
integrateCurve :: (AbstractAdd p v,AbstractScale v,AbstractZero p) => SamplingAlgorithm v -> Curve v -> p -> p
integrateCurve samplingAlgorithm c initial_value = foldl' add initial_value $ map intervalSingleIntegral $ samplingAlgorithm c
-- | Sampling algorithm that takes a fixed count of samples.
linearSamples :: Integer -> SamplingAlgorithm a
linearSamples n c = map (\(l,h) -> intervalSample c l h) $ doubles $ zeroToOne (n+1)
-- | Sampling algorithm that takes increasing numbers of samples over intervals where the magnitude of the sample is large.
adaptiveMagnitudeSamples :: (AbstractMagnitude a) => Integer -> SamplingAlgorithm a
adaptiveMagnitudeSamples n c = resampleLoop (\xs -> if genericLength xs > n then Nothing else Just $ newSamples xs) $ linearSamples (max 1 $ n `div` 10) c
where newSamples xs = let a = abstractAverage $ map intervalMagnitude xs
in flip concatMap xs $ \x -> if intervalMagnitude x >= a then splitInterval x else [x]
intervalMagnitude :: (AbstractMagnitude a) => IntervalSample a -> RSdouble
intervalMagnitude (IntervalSample _ l h a) = magnitude a * (abs $ h-l)
-- | Loop to keep generating samples until finished.
resampleLoop :: (b -> Maybe b) -> b -> b
resampleLoop nextPass initial_value = f $ initial_value
where f x = maybe x f $ nextPass x