-- Copyright (c) 2000 Galois Connections, Inc.
-- All rights reserved.  This software is distributed as
-- free software under the license in the file "LICENSE",
-- which is included in the distribution.

module Surface
    ( SurfaceFn (..)
    , Properties
    , sfun, sconst
    , prop
    , matte, shiny
    , chgColor
    , surface
    ) where

import Geometry
import CSG
import Misc

-- the surface gets passed face then u then v.
data SurfaceFn c v = SFun (Int -> Double -> Double -> Properties c v)
                   | SConst (Properties c v)

sfun :: (Int -> Double -> Double -> Properties c v) -> SurfaceFn c v
sfun = SFun
sconst :: Properties c v -> SurfaceFn c v
sconst = SConst

type Properties c v = (c, v, v, v)

prop c d s p = (c, d, s, p)

matte = (white, 1.0, 0.0, 1.0)
shiny = (white, 0.0, 1.0, 1.0)

chgColor :: c -> Properties d v -> Properties c v
chgColor c (_, d, s, p) = (c, d, s, p)

instance (Show c, Show v) => Show (SurfaceFn c v) where
  show (SFun _)   = "Surface function"
  -- show (SConst p) = "Surface constant: " ++ show p
  show (SConst p) = "Surface constant"

evalSurface :: SurfaceFn Color Double -> Int -> Double -> Double -> Properties Color Double
evalSurface (SConst p) = \_ _ _ -> p
evalSurface (SFun f)   = f

-- calculate surface properties, given the type of
-- surface, and intersection point in object coordinates

-- surface :: Surface SurfaceFn -> (Int, Point) -> (Vector, Properties)

surface (Planar _ v0 v1) (n, p0, fn)
  = (norm, evalSurface fn n' u v)
  where norm = normalize $ cross v0 v1
        (n', u, v) = planarUV n p0

surface (Spherical _ v0 v1) (_, p0, fn)
  = (norm, evalSurface fn 0 u v)
  where x = xCoord p0
        y = yCoord p0
        z = zCoord p0
        k = sqrt (1 - sq y)
        theta = adjustRadian (atan2 (x / k) (z / k))
        -- correct so that the image grows left-to-right
        -- instead of right-to-left
        u = 1.0 - clampf (theta / (2 * pi))
        v =       clampf ((y + 1) / 2)
        norm = normalize $ cross v0 v1

-- ZZ ignore the (incorrect) surface model, and estimate the normal
-- from the intersection in object space
surface (Cylindrical _ v0 v1) (_, p0, fn)
  = (norm, evalSurface fn 0 u v)
  where x = xCoord p0
        y = yCoord p0
        z = zCoord p0
        u = clampf $ adjustRadian (atan2 x z) / (2 * pi)
        v = y
        norm = normalize $ cross v0 v1

-- ZZ ignore the (incorrect) surface model, and estimate the normal
-- from the intersection in object space
surface (Conic _ v0 v1) (_, p0, fn)
  = (norm, evalSurface fn 0 u v)
  where x = xCoord p0
        y = yCoord p0
        z = zCoord p0
        u = clampf $ adjustRadian (atan2 (x / y) (z / y)) / (2 * pi)
        v = y
        norm = normalize $ cross v0 v1

planarUV face p0
  = case face of
    PlaneFace      -> (0, x, z)

    CubeFront      -> (0, x, y)
    CubeBack       -> (1, x, y)
    CubeLeft       -> (2, z, y)
    CubeRight      -> (3, z, y)
    CubeTop        -> (4, x, z)
    CubeBottom     -> (5, x, z)

    CylinderTop    -> (1, (x + 1) / 2, (z + 1) / 2)
    CylinderBottom -> (2, (x + 1) / 2, (z + 1) / 2)

    ConeBase       -> (1, (x + 1) / 2, (z + 1) / 2)
  where x = xCoord p0
        y = yCoord p0
        z = zCoord p0

-- misc

adjustRadian :: Radian -> Radian
adjustRadian r = if r > 0 then r else r + 2 * pi
