L-seed-0.2: src/Lseed/Logic.hs
-- | This module is mostly a general dump...
module Lseed.Logic where
import Lseed.Data
import Lseed.Data.Functions
import Lseed.Grammar.Parse
import Lseed.LSystem
import Lseed.Constants
import Lseed.Geometry
import Lseed.StipeInfo
import System.Time
import Text.Printf
import System.Random
import Data.List
import qualified Data.Foldable as F
timeSpanFraction :: Double -> ClockTime -> ClockTime -> Double
timeSpanFraction spanLenght (TOD sa pa) (TOD sb pb) =
min 1 $ max 0 $
(fromIntegral $ (sb - sa) * 1000000000000 + (pb-pa)) /
(spanLenght * 1000000000000 )
formatTimeInfo :: Integer -> Double -> String
formatTimeInfo day frac = let minutes = floor (frac * 12 * 60) :: Integer
(hour, minute) = divMod minutes 60
in printf "Day %d %2d:%02d" day (6+hour) minute
-- | Given the fraction of the time passed, returnes the angle of the sunlight
lightAngle :: Double -> Angle
lightAngle diff = pi/100 + diff * (98*pi/100)
-- | Calculates the length to be grown
remainingGrowth :: (a -> GrowthState) -> Planted a -> Double
remainingGrowth getGrowths planted = go (phenotype planted)
where go p@(Plant { pLength = l1, pBranches = ps }) =
sum (map go ps) + case getGrowths (pData p) of
NoGrowth -> 0
EnlargingTo l2 -> l2 - l1
GrowingSeed done -> (1-done) * seedGrowthCost
-- | For a GrowingGarden, calculates the current amount of light and then
-- advance the growth. This ought to be called after applyGenome
growGarden :: (RandomGen g) => Angle -> g -> GrowingGarden -> (Double -> GrowingGarden)
growGarden angle rgen garden = sequence $ zipWith growPlanted garden totalLight
where totalLight = map (plantTotalSum . fmap snd . phenotype) $ lightenGarden angle garden
-- | For all Growing plants that are done, find out the next step
-- If new plants are to be created, these are returned via their position, next
-- to their parent plant.
applyGenome :: (RandomGen g) => Angle -> g -> GrowingGarden -> [(GrowingPlanted,[Double])]
applyGenome angle rgen garden = zipWith applyGenome' rgens aGarden
where rgens = unfoldr (Just . split) rgen
aGarden = annotateGarden angle garden
applyGenome' rgen planted =
if remainingGrowth siGrowth planted < eps
then ( planted { phenotype = applyLSystem rgen
(genome planted)
(phenotype planted)
-- here, we throw away the last eps of growth. Is that a problem?
}
, collectSeeds rgen planted)
else (fmap siGrowth planted,[])
collectSeeds :: (RandomGen g) => g -> AnnotatedPlanted -> [Double]
collectSeeds rgen planted = snd $ F.foldr go (rgen,[]) planted
where go si (rgen,seedPoss) = case siGrowth si of
GrowingSeed _ ->
let spread = ( - siHeight si + siOffset si
, siHeight si + siOffset si
)
(posDelta,rgen') = randomR spread rgen
in (rgen', posDelta:seedPoss)
_ -> (rgen,seedPoss)
-- | Applies an L-System to a Plant, putting the new length in the additional
-- information field
growPlanted :: GrowingPlanted -> Double -> (Double -> GrowingPlanted)
growPlanted planted light =
let remainingLength = remainingGrowth id planted
in if remainingLength > eps
then let sizeOfPlant = weightedPlantLength (phenotype planted)
lightAvailable = light - costPerLength * sizeOfPlant
lowerBound = if sizeOfPlant < smallPlantBoostSize && not (doesBlossom (phenotype planted))
then (1 - sizeOfPlant / smallPlantBoostSize) * smallPlantBoostLength
else 0
allowedGrowths = max lowerBound $
(growthPerDayAndLight * lightAvailable) /
(fromIntegral ticksPerDay)
growthThisTick = min remainingLength allowedGrowths
growthFraction = growthThisTick / remainingLength
in \tickDiff -> applyGrowth (tickDiff * growthFraction) planted
else const planted
doesBlossom (Plant { pData = (GrowingSeed _) }) = True
doesBlossom (Plant { pBranches = ps }) = any doesBlossom ps
-- | Applies Growth at given fraction, leaving the target length in place
applyGrowth :: Double -> GrowingPlanted -> GrowingPlanted
applyGrowth r = mapPlanted (applyGrowth' (\a b -> a * (1-r) + b * r))
applyGrowth' :: (Double -> Double -> Double) -> GrowingPlant -> GrowingPlant
applyGrowth' f = go
where go (Plant NoGrowth l ang ut ps) =
Plant NoGrowth l ang ut (map go ps)
go (Plant (EnlargingTo l2) l1 ang ut ps) =
Plant (EnlargingTo l2) (f l1 l2) ang ut (map go ps)
go (Plant (GrowingSeed done) l ang ut ps) =
Plant (GrowingSeed (f (done*seedGrowthCost) seedGrowthCost)) l ang ut (map go ps)