diff --git a/diagrams-lib.cabal b/diagrams-lib.cabal index 2ee71ba7..2f8fe93b 100644 --- a/diagrams-lib.cabal +++ b/diagrams-lib.cabal @@ -75,6 +75,7 @@ Library Diagrams.ThreeD.Transform, Diagrams.ThreeD.Camera, Diagrams.ThreeD.Light, + Diagrams.ThreeD Diagrams.Animation, Diagrams.Animation.Active, Diagrams.Util, diff --git a/src/Diagrams/Coordinates.hs b/src/Diagrams/Coordinates.hs index 253141f0..487adaff 100644 --- a/src/Diagrams/Coordinates.hs +++ b/src/Diagrams/Coordinates.hs @@ -18,9 +18,15 @@ ----------------------------------------------------------------------------- module Diagrams.Coordinates - ( (:&)(..), Coordinates(..) ) + ( (:&)(..), Coordinates(..) + + -- * Lenses for particular axes + , HasX(..), HasY(..), HasZ(..) + ) where +import Control.Lens (Lens') + import Diagrams.Core.Points -- | A pair of values, with a convenient infix (left-associative) @@ -104,3 +110,15 @@ instance Coordinates v => Coordinates (Point v) where x & y = P (x & y) coords (P v) = coords v + +-- | The class of types with at least one coordinate, called _x. +class HasX t where + _x :: Lens' t Double + +-- | The class of types with at least two coordinates, the second called _y. +class HasY t where + _y :: Lens' t Double + +-- | The class of types with at least three coordinates, the third called _z. +class HasZ t where + _z :: Lens' t Double diff --git a/src/Diagrams/ThreeD.hs b/src/Diagrams/ThreeD.hs new file mode 100644 index 00000000..6d42a474 --- /dev/null +++ b/src/Diagrams/ThreeD.hs @@ -0,0 +1,48 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE TypeSynonymInstances #-} +----------------------------------------------------------------------------- +-- | +-- Module : Diagrams.ThreeD +-- Copyright : (c) 2013 diagrams-lib team (see LICENSE) +-- License : BSD-style (see LICENSE) +-- Maintainer : diagrams-discuss@googlegroups.com +-- +-- This module defines the three-dimensional vector space R^3, +-- three-dimensional transformations, and various predefined +-- three-dimensional shapes. This module re-exports useful +-- functionality from a group of more specific modules: +-- +-- * "Diagrams.ThreeD.Types" defines basic types for two-dimensional +-- diagrams, including types representing the 3D Euclidean vector +-- space and various systems of representing directions. +-- +-- * "Diagrams.ThreeD.Transform" defines R^3-specific transformations +-- such as rotation by an angle, and scaling, translation, and +-- reflection in the X, Y, and Z directions. +-- +-- * "Diagrams.ThreeD.Shapes" defines three-dimensional solids, +-- e.g. spheres and cubes. +-- +-- * "Diagrams.ThreeD.Vector" defines some special 3D vectors and +-- functions for converting between vectors and directions. +-- +-- * "Diagrams.ThreeD.Light" and "Diagrams.ThreeD.Camera" define types needed +-- for rendering 3D geometry to (2D) images. +----------------------------------------------------------------------------- +module Diagrams.ThreeD + ( module Diagrams.ThreeD.Types + , module Diagrams.ThreeD.Transform + , module Diagrams.ThreeD.Vector + , module Diagrams.ThreeD.Shapes + , module Diagrams.ThreeD.Light + , module Diagrams.ThreeD.Camera + ) where + + +import Diagrams.ThreeD.Transform +import Diagrams.ThreeD.Vector +import Diagrams.ThreeD.Light +import Diagrams.ThreeD.Types +import Diagrams.ThreeD.Camera +import Diagrams.ThreeD.Shapes diff --git a/src/Diagrams/ThreeD/Align.hs b/src/Diagrams/ThreeD/Align.hs new file mode 100644 index 00000000..4ea6bbeb --- /dev/null +++ b/src/Diagrams/ThreeD/Align.hs @@ -0,0 +1,209 @@ +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE TypeFamilies #-} +----------------------------------------------------------------------------- +-- | +-- Module : Diagrams.ThreeD.Align +-- Copyright : (c) 2013 diagrams-lib team (see LICENSE) +-- License : BSD-style (see LICENSE) +-- Maintainer : diagrams-discuss@googlegroups.com +-- +-- Alignment combinators specialized for three dimensions. See +-- "Diagrams.Align" for more general alignment combinators. +-- +-- The basic idea is that alignment is achieved by moving diagrams' +-- local origins relative to their envelopes or traces (or some other +-- sort of boundary). For example, to align several diagrams along +-- their tops, we first move their local origins to the upper edge of +-- their boundary (using e.g. @map 'alignZMax'@), and then put them +-- together with their local origins along a line (using e.g. 'cat' +-- from "Diagrams.Combinators"). +-- +----------------------------------------------------------------------------- + +module Diagrams.TwoD.Align + ( -- * Absolute alignment + -- ** Align by envelope + alignXMin, alignXMax, alignYMin, alignYMax, alignZMin, alignZMax + + -- ** Align by trace + , snugXMin, snugXMax, snugYMin, snugYMax, snugZMin, snugZMax + + -- * Relative alignment + , alignX, snugX, alignY, snugY, alignZ, snugZ + + -- * Centering + , centerX, centerY, centerZ + , centerXY, centerXZ, centerYZ + , snugCenterX, snugCenterY, snugCenterZ + , snugCenterXY, snugCenterXZ, snugCenterYZ + + ) where + +import Diagrams.Core + +import Diagrams.Align +import Diagrams.ThreeD.Types +import Diagrams.ThreeD.Vector + +import Data.VectorSpace + +-- | Translate the diagram along unitX so that all points have +-- positive x-values. +alignXMin :: (Alignable a, HasOrigin a, V a ~ R3) => a -> a +alignXMin = align (negateV unitX) + +snugXMin :: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => a -> a +snugXMin = snug (negateV unitX) + +-- | Translate the diagram along unitX so that all points have +-- negative x-values. +alignXMax :: (Alignable a, HasOrigin a, V a ~ R3) => a -> a +alignXMax = align unitX + +snugXMax :: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => a -> a +snugXMax = snug unitX + + +-- | Translate the diagram along unitY so that all points have +-- negative y-values. +alignYMax :: (Alignable a, HasOrigin a, V a ~ R3) => a -> a +alignYMax = align unitY + +-- | Move the origin along unitY until it touches the edge of the +-- diagram. +snugYMax:: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => a -> a +snugYMax = snug unitY + +-- | Translate the diagram along unitY so that all points have +-- positive y-values. +alignYMin :: (Alignable a, HasOrigin a, V a ~ R3) => a -> a +alignYMin = align (negateV unitY) + +snugYMin :: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => a -> a +snugYMin = snug (negateV unitY) + +-- | Translate the diagram along unitZ so that all points have +-- negative z-values. +alignZMax :: (Alignable a, HasOrigin a, V a ~ R3) => a -> a +alignZMax = align unitZ + +-- | Move the origin along unitZ until it touches the edge of the +-- diagram. +snugZMax:: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => a -> a +snugZMax = snug unitZ + +-- | Translate the diagram along unitZ so that all points have +-- positive z-values. +alignZMin :: (Alignable a, HasOrigin a, V a ~ R3) => a -> a +alignZMin = align (negateV unitZ) + +-- | Move the origin along unit_Z until it touches the edge of the +-- diagram. +snugZMin :: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => a -> a +snugZMin = snug (negateV unitZ) + +-- | @alignX@ and @snugX@ move the local origin along unitX as follows: +-- +-- * @alignX (-1)@ moves the local origin to the low-x of the boundary; +-- +-- * @align 1@ moves the local origin to the high-x edge; +-- +-- * any other argument interpolates linearly between these. For +-- example, @alignX 0@ centers, @alignX 2@ moves the origin one +-- \"radius\" to the right of the right edge, and so on. +-- +-- * @snugX@ works the same way. + +alignX :: (Alignable a, HasOrigin a, V a ~ R3) => Double -> a -> a +alignX = alignBy unitX + +-- | See the documentation for 'alignX'. +snugX :: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => Double -> a -> a +snugX = snugBy unitX + +-- | Like 'alignX', but moving the local origin vertically, with an +-- argument of @1@ corresponding to the top edge and @(-1)@ corresponding +-- to the bottom edge. +alignY :: (Alignable a, HasOrigin a, V a ~ R3) => Double -> a -> a +alignY = alignBy unitY + +snugY :: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => Double -> a -> a +snugY = snugBy unitY + + +-- | Like 'alignX', but moving the local origin in the Z direction, with an +-- argument of @1@ corresponding to the top edge and @(-1)@ corresponding +-- to the bottom edge. +alignZ :: (Alignable a, HasOrigin a, V a ~ R3) => Double -> a -> a +alignZ = alignBy unitZ + +snugZ :: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => Double -> a -> a +snugZ = snugBy unitZ + +-- | Center the local origin along the X-axis. +centerX :: (Alignable a, HasOrigin a, V a ~ R3) => a -> a +centerX = alignBy unitX 0 + +snugCenterX :: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => a -> a +snugCenterX = snugBy unitX 0 + +-- | Center the local origin along the Y-axis. +centerY :: (Alignable a, HasOrigin a, V a ~ R3) => a -> a +centerY = alignBy unitY 0 + +snugCenterY :: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => a -> a +snugCenterY = snugBy unitY 0 + +-- | Center the local origin along the Z-axis. +centerZ :: (Alignable a, HasOrigin a, V a ~ R3) => a -> a +centerZ = alignBy unitZ 0 + +snugCenterZ :: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => a -> a +snugCenterZ = snugBy unitZ 0 + +-- | Center along both the X- and Y-axes. +centerXY :: (Alignable a, HasOrigin a, V a ~ R3) => a -> a +centerXY = centerX . centerY + +snugCenterXY :: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => a -> a +snugCenterXY = snugCenterX . snugCenterY + + +-- | Center along both the X- and Z-axes. +centerXZ :: (Alignable a, HasOrigin a, V a ~ R3) => a -> a +centerXZ = centerX . centerZ + +snugCenterXZ :: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => a -> a +snugCenterXZ = snugCenterX . snugCenterZ + + +-- | Center along both the Y- and Z-axes. +centerYZ :: (Alignable a, HasOrigin a, V a ~ R3) => a -> a +centerYZ = centerZ . centerY + +snugCenterYZ :: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => a -> a +snugCenterYZ = snugCenterZ . snugCenterY + +-- | Center an object in three dimensions. +centerXYZ :: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => a -> a +centerXYZ = centerX . centerY . centerZ + +snugCenterXYZ :: (Fractional (Scalar (V a)), Alignable a, Traced a, + HasOrigin a, V a ~ R3) => a -> a +snugCenterXYZ = snugCenterX . snugCenterY . snugCenterZ diff --git a/src/Diagrams/ThreeD/Transform.hs b/src/Diagrams/ThreeD/Transform.hs index 2f277674..475f7c31 100644 --- a/src/Diagrams/ThreeD/Transform.hs +++ b/src/Diagrams/ThreeD/Transform.hs @@ -25,6 +25,7 @@ import Diagrams.Coordinates import Diagrams.ThreeD.Types import Diagrams.ThreeD.Vector +import Control.Lens ((*~), (//~)) import Data.Semigroup import Data.AffineSpace @@ -75,13 +76,13 @@ aboutY ang = fromLinear r (linv r) where -- | @rotationAbout p d a@ is a rotation about a line parallel to @d@ -- passing through @p@. -rotatationAbout +rotationAbout :: (Angle a, Direction d) => P3 -- ^ origin of rotation -> d -- ^ direction of rotation axis -> a -- ^ angle of rotation -> T3 -rotatationAbout p d a +rotationAbout p d a = mconcat [translation (negateV t), fromLinear r (linv r), translation t] where @@ -94,6 +95,62 @@ rotatationAbout p d a w ^* ((w <.> v) * (1 - cos th)) t = p .-. origin +-- | @pointAt about initial final@ produces a rotation which brings +-- the direction @initial@ to point in the direction @final@ by first +-- panning around @about@, then tilting about the axis perpendicular +-- to initial and final. In particular, if this can be accomplished +-- without tilting, it will be, otherwise if only tilting is +-- necessary, no panning will occur. The tilt will always be between +-- ± 1/4 turn. +pointAt :: Direction d => d -> d -> d -> T3 +pointAt a i f = pointAt' (fromDirection a) (fromDirection i) (fromDirection f) + +-- | pointAt' has the same behavior as 'pointAt', but takes vectors +-- instead of directions. +pointAt' :: R3 -> R3 -> R3 -> T3 +pointAt' about initial final = tilt <> pan where + inPanPlane = final ^-^ project final initial + panAngle = angleBetween initial inPanPlane :: Turn + pan = rotationAbout origin (direction about :: Spherical Turn) panAngle + tiltAngle = angleBetween initial inPanPlane :: Turn + tiltDir = direction $ cross3 inPanPlane about :: Spherical Turn + tilt = rotationAbout origin tiltDir tiltAngle + +-- Scaling ------------------------------------------------- + +-- | Construct a transformation which scales by the given factor in +-- the x direction. +scalingX :: Double -> T3 +scalingX c = fromLinear s s + where s = (_x *~ c) <-> (_x //~ c) + +-- | Scale a diagram by the given factor in the x (horizontal) +-- direction. To scale uniformly, use 'scale'. +scaleX :: (Transformable t, V t ~ R3) => Double -> t -> t +scaleX = transform . scalingX + +-- | Construct a transformation which scales by the given factor in +-- the y direction. +scalingY :: Double -> T3 +scalingY c = fromLinear s s + where s = (_y *~ c) <-> (_y //~ c) + +-- | Scale a diagram by the given factor in the y (vertical) +-- direction. To scale uniformly, use 'scale'. +scaleY :: (Transformable t, V t ~ R3) => Double -> t -> t +scaleY = transform . scalingY + +-- | Construct a transformation which scales by the given factor in +-- the z direction. +scalingZ :: Double -> T3 +scalingZ c = fromLinear s s + where s = (_z *~ c) <-> (_z //~ c) + +-- | Scale a diagram by the given factor in the z direction. To scale +-- uniformly, use 'scale'. +scaleZ :: (Transformable t, V t ~ R3) => Double -> t -> t +scaleZ = transform . scalingZ + -- | Get the matrix equivalent of an affine transform, as a triple of -- columns paired with the translation vector. This is mostly -- useful for implementing backends. diff --git a/src/Diagrams/ThreeD/Types.hs b/src/Diagrams/ThreeD/Types.hs index 3ab174a7..24b5aac7 100644 --- a/src/Diagrams/ThreeD/Types.hs +++ b/src/Diagrams/ThreeD/Types.hs @@ -21,16 +21,16 @@ module Diagrams.ThreeD.Types ( -- * 3D Euclidean space - R3, r3, unr3 + R3(..), r3, unr3 , P3, p3, unp3 , T3 + , r3Iso -- * Two-dimensional angles -- | These are defined in "Diagrams.TwoD.Types" but -- reëxported here for convenience. , Angle(..) - , Turn(..), Rad(..), Deg(..) - + , Turn(..), asTurn, CircleFrac, Rad(..), asRad, Deg(..), asDeg , fullTurn, convertAngle, angleRatio -- * Directions in 3D @@ -40,7 +40,7 @@ module Diagrams.ThreeD.Types ) where import Control.Applicative -import Control.Lens (Iso', iso, over) +import Control.Lens (Iso', iso, over, _1, _2, _3) import Diagrams.Coordinates import Diagrams.Core @@ -99,10 +99,13 @@ type P3 = Point R3 p3 :: (Double, Double, Double) -> P3 p3 = P . R3 --- | Convert a 2D point back into a triple of coordinates. +-- | Convert a 3D point back into a triple of coordinates. unp3 :: P3 -> (Double, Double, Double) unp3 = unR3 . unPoint +p3Iso :: Iso' P3 (Double, Double, Double) +p3Iso = iso unp3 p3 + -- | Transformations in R^3. type T3 = Transformation R3 @@ -149,3 +152,21 @@ instance (Angle a) => Direction (Spherical a) where -- intermediate type would be ambiguous. asSpherical :: Spherical Turn -> Spherical Turn asSpherical = id + +instance HasX R3 where + _x = r3Iso . _1 + +instance HasX P3 where + _x = p3Iso . _1 + +instance HasY R3 where + _y = r3Iso . _2 + +instance HasY P3 where + _y = p3Iso . _2 + +instance HasZ R3 where + _z = r3Iso . _3 + +instance HasZ P3 where + _z = p3Iso . _3 diff --git a/src/Diagrams/ThreeD/Vector.hs b/src/Diagrams/ThreeD/Vector.hs index 56d9ee3c..877ec3d7 100644 --- a/src/Diagrams/ThreeD/Vector.hs +++ b/src/Diagrams/ThreeD/Vector.hs @@ -17,7 +17,7 @@ module Diagrams.ThreeD.Vector unitX, unitY, unitZ, unit_X, unit_Y, unit_Z, -- * Converting between vectors and angles - direction, fromDirection, angleBetween + direction, fromDirection, angleBetween, angleBetweenDirs ) where import Data.VectorSpace @@ -76,3 +76,7 @@ fromDirection (toSpherical -> (Spherical θ' φ')) = r3 (x,y,z) where angleBetween :: (Angle a, Num a, Ord a) => R3 -> R3 -> a angleBetween v1 v2 = convertAngle . Rad $ atan2 (magnitude $ cross3 v1 v2) (v1 <.> v2) + +-- | compute the positive angle between the two vectors in their common plane +angleBetweenDirs :: (Direction d, Angle a, Num a, Ord a) => d -> d -> a +angleBetweenDirs d1 d2 = angleBetween (fromDirection d1) (fromDirection d2) diff --git a/src/Diagrams/TwoD/Types.hs b/src/Diagrams/TwoD/Types.hs index 2a7fd3a6..22dd1bdc 100644 --- a/src/Diagrams/TwoD/Types.hs +++ b/src/Diagrams/TwoD/Types.hs @@ -20,8 +20,8 @@ module Diagrams.TwoD.Types ( -- * 2D Euclidean space - R2(..), r2, unr2 - , P2, p2, unp2 + R2(..), r2, unr2, r2Iso + , P2, p2, unp2, p2Iso , T2 -- * Angles @@ -40,7 +40,7 @@ import Data.NumInstances.Tuple () import Data.VectorSpace import Data.Typeable - +import Control.Lens (Iso', iso, _1, _2) ------------------------------------------------------------ -- 2D Euclidean space @@ -151,6 +151,15 @@ instance Coordinates R2 where x & y = R2 x y coords (R2 x y) = x :& y +r2Iso :: Iso' R2 (Double, Double) +r2Iso = iso unr2 r2 + +instance HasX R2 where + _x = r2Iso . _1 + +instance HasY R2 where + _y = r2Iso . _2 + -- | Points in R^2. This type is intentionally abstract. -- -- * To construct a point, use 'p2', or '&' (see @@ -190,6 +199,14 @@ type T2 = Transformation R2 instance Transformable R2 where transform = apply +p2Iso :: Iso' P2 (Double, Double) +p2Iso = iso unp2 p2 + +instance HasX P2 where + _x = p2Iso . _1 + +instance HasY P2 where + _y = p2Iso . _2 ------------------------------------------------------------ -- Angles