{-# LANGUAGE TypeFamilies
, FlexibleInstances
, FlexibleContexts
, UndecidableInstances
, GeneralizedNewtypeDeriving
, StandaloneDeriving
, MultiParamTypeClasses
#-}------------------------------------------------------------------------------- |-- Module : Graphics.Rendering.Diagrams.Envelope-- Copyright : (c) 2011 diagrams-core team (see LICENSE)-- License : BSD-style (see LICENSE)-- Maintainer : diagrams-discuss@googlegroups.com---- "Graphics.Rendering.Diagrams" defines the core library of primitives-- forming the basis of an embedded domain-specific language for-- describing and rendering diagrams.---- The @Envelope@ module defines a data type and type class for-- \"envelopes\", aka functional bounding regions.-------------------------------------------------------------------------------moduleDiagrams.Core.Envelope(-- * EnvelopesEnvelope(..),inEnvelope,appEnvelope,onEnvelope,mkEnvelope,pointEnvelope,Enveloped(..)-- * Utility functions,diameter,radius,envelopeVMay,envelopeV,envelopePMay,envelopeP,envelopeSMay,envelopeS-- * Miscellaneous,OrderedField)whereimportControl.Applicative((<$>))importqualifiedData.MapasMimportData.Maybe(fromMaybe)importData.SemigroupimportqualifiedData.SetasSimportData.AffineSpace((.+^),(.-^))importData.VectorSpaceimportDiagrams.Core.HasOriginimportDiagrams.Core.PointsimportDiagrams.Core.TransformimportDiagrams.Core.V-------------------------------------------------------------- Envelopes ----------------------------------------------------------------------------------------------------------- | Every diagram comes equipped with an /envelope/. What is an envelope?---- Consider first the idea of a /bounding box/. A bounding box-- expresses the distance to a bounding plane in every direction-- parallel to an axis. That is, a bounding box can be thought of-- as the intersection of a collection of half-planes, two-- perpendicular to each axis.---- More generally, the intersection of half-planes in /every/-- direction would give a tight \"bounding region\", or convex hull.-- However, representing such a thing intensionally would be-- impossible; hence bounding boxes are often used as an-- approximation.---- An envelope is an /extensional/ representation of such a-- \"bounding region\". Instead of storing some sort of direct-- representation, we store a /function/ which takes a direction as-- input and gives a distance to a bounding half-plane as output.-- The important point is that envelopes can be composed, and-- transformed by any affine transformation.---- Formally, given a vector @v@, the envelope computes a scalar @s@ such-- that---- * for every point @u@ inside the diagram,-- if the projection of @(u - origin)@ onto @v@ is @s' *^ v@, then @s' <= s@.---- * @s@ is the smallest such scalar.---- There is also a special \"empty envelope\".---- The idea for envelopes came from-- Sebastian Setzer; see-- <http://byorgey.wordpress.com/2009/10/28/collecting-attributes/#comment-2030>. See also Brent Yorgey, /Monoids: Theme and Variations/, published in the 2012 Haskell Symposium: <http://www.cis.upenn.edu/~byorgey/pub/monoid-pearl.pdf>; video: <http://www.youtube.com/watch?v=X-8NCkD2vOw>.newtypeEnvelopev=Envelope{unEnvelope::Option(v->Max(Scalarv))}inEnvelope::(Option(v->Max(Scalarv))->Option(v->Max(Scalarv)))->Envelopev->EnvelopevinEnvelopef=Envelope.f.unEnvelopeappEnvelope::Envelopev->Maybe(v->Scalarv)appEnvelope(Envelope(Optione))=(getMax.)<$>eonEnvelope::((v->Scalarv)->(v->Scalarv))->Envelopev->EnvelopevonEnvelopet=(inEnvelope.fmap)((Max.).t.(getMax.))mkEnvelope::(v->Scalarv)->EnvelopevmkEnvelope=Envelope.Option.Just.(Max.)-- | Create an envelope for the given point.pointEnvelope::(Fractional(Scalarv),InnerSpacev)=>Pointv->EnvelopevpointEnvelopep=moveTop(mkEnvelope(constzeroV))-- | Envelopes form a semigroup with pointwise maximum as composition.-- Hence, if @e1@ is the envelope for diagram @d1@, and-- @e2@ is the envelope for @d2@, then @e1 \`mappend\` e2@-- is the envelope for @d1 \`atop\` d2@.derivinginstanceOrd(Scalarv)=>Semigroup(Envelopev)-- | The special empty envelope is the identity for the-- 'Monoid' instance.derivinginstanceOrd(Scalarv)=>Monoid(Envelopev)-- XXX add some diagrams here to illustrate! Note that Haddock supports-- inline images, using a \<\<url\>\> syntax.typeinstanceV(Envelopev)=v-- | The local origin of an envelope is the point with respect to-- which bounding queries are made, /i.e./ the point from which the-- input vectors are taken to originate.instance(InnerSpacev,Fractional(Scalarv))=>HasOrigin(Envelopev)wheremoveOriginTo(Pu)=onEnvelope$\fv->fv^-^((u^/(v<.>v))<.>v)instanceShow(Envelopev)whereshow_="<envelope>"-------------------------------------------------------------- Transforming envelopes ---------------------------------------------------------------------------------------------- XXX can we get away with removing this Floating constraint? It's the-- call to normalized here which is the culprit.instance(HasLinearMapv,InnerSpacev,Floating(Scalarv))=>Transformable(Envelopev)wheretransformt=-- XXX add lots of comments explaining this!moveOriginTo(P.negateV.transl$t).(onEnvelope$\fv->letv'=normalized$lapp(transpt)vvi=apply(invt)vinfv'/(v'<.>vi))-------------------------------------------------------------- Enveloped class-------------------------------------------------------------- | When dealing with envelopes we often want scalars to be an-- ordered field (i.e. support all four arithmetic operations and be-- totally ordered) so we introduce this class as a convenient-- shorthand.class(Fractionals,Floatings,Ords,AdditiveGroups)=>OrderedFieldsinstance(Fractionals,Floatings,Ords,AdditiveGroups)=>OrderedFields-- | @Enveloped@ abstracts over things which have an envelope.class(InnerSpace(Va),OrderedField(Scalar(Va)))=>Envelopedawhere-- | Compute the envelope of an object. For types with an intrinsic-- notion of \"local origin\", the envelope will be based there.-- Other types (e.g. 'Trail') may have some other default-- reference point at which the envelope will be based; their-- instances should document what it is.getEnvelope::a->Envelope(Va)instance(InnerSpacev,OrderedField(Scalarv))=>Enveloped(Envelopev)wheregetEnvelope=idinstance(OrderedField(Scalarv),InnerSpacev)=>Enveloped(Pointv)wheregetEnvelopep=moveTop.mkEnvelope$constzeroVinstance(Envelopeda,Envelopedb,Va~Vb)=>Enveloped(a,b)wheregetEnvelope(x,y)=getEnvelopex<>getEnvelopeyinstance(Envelopedb)=>Enveloped[b]wheregetEnvelope=mconcat.mapgetEnvelopeinstance(Envelopedb)=>Enveloped(M.Mapkb)wheregetEnvelope=mconcat.mapgetEnvelope.M.elemsinstance(Envelopedb)=>Enveloped(S.Setb)wheregetEnvelope=mconcat.mapgetEnvelope.S.elems-------------------------------------------------------------- Computing with envelopes-------------------------------------------------------------- | Compute the vector from the local origin to a separating-- hyperplane in the given direction, or @Nothing@ for the empty-- envelope.envelopeVMay::Envelopeda=>Va->a->Maybe(Va)envelopeVMayv=fmap((*^v).($v)).appEnvelope.getEnvelope-- | Compute the vector from the local origin to a separating-- hyperplane in the given direction. Returns the zero vector for-- the empty envelope.envelopeV::Envelopeda=>Va->a->VaenvelopeVv=fromMaybezeroV.envelopeVMayv-- | Compute the point on a separating hyperplane in the given-- direction, or @Nothing@ for the empty envelope.envelopePMay::Envelopeda=>Va->a->Maybe(Point(Va))envelopePMayv=fmapP.envelopeVMayv-- | Compute the point on a separating hyperplane in the given-- direction. Returns the origin for the empty envelope.envelopeP::Envelopeda=>Va->a->Point(Va)envelopePv=P.envelopeVv-- | Equivalent to the magnitude of 'envelopeVMay':---- @ envelopeSMay v x == fmap magnitude (envelopeVMay v x) @---- (other than differences in rounding error)---- Note that the 'envelopeVMay' / 'envelopePMay' functions above should be-- preferred, as this requires a call to magnitude. However, it is more-- efficient than calling magnitude on the results of those functions.envelopeSMay::Envelopeda=>Va->a->Maybe(Scalar(Va))envelopeSMayv=fmap((*magnitudev).($v)).appEnvelope.getEnvelope-- | Equivalent to the magnitude of 'envelopeV':---- @ envelopeS v x == magnitude (envelopeV v x) @---- (other than differences in rounding error)---- Note that the 'envelopeV' / 'envelopeP' functions above should be-- preferred, as this requires a call to magnitude. However, it is more-- efficient than calling magnitude on the results of those functions.envelopeS::(Envelopeda,Num(Scalar(Va)))=>Va->a->Scalar(Va)envelopeSv=fromMaybe0.envelopeSMayv-- | Compute the diameter of a enveloped object along a particular-- vector. Returns zero for the empty envelope.diameter::Envelopeda=>Va->a->Scalar(Va)diameterva=caseappEnvelope$getEnvelopeaof(Justenv)->(envv-env(negateVv))*magnitudevNothing->0-- | Compute the \"radius\" (1\/2 the diameter) of an enveloped object-- along a particular vector.radius::Envelopeda=>Va->a->Scalar(Va)radiusv=(0.5*).diameterv