|
| 1 | +module Main where |
| 2 | + |
| 3 | +import Data.Tuple (swap) |
| 4 | +import GHC.Word (Word8) |
| 5 | +import Codec.Picture |
| 6 | +import Codec.Picture.Types |
| 7 | + |
| 8 | +-- Where is camera pointed ? |
| 9 | +maxX = 480 |
| 10 | +maxY = 240 |
| 11 | + |
| 12 | +main :: IO () |
| 13 | +main = do |
| 14 | + let objects = [ --Line (Point 0 0 0) (Vector 1 2 1) |
| 15 | + --, Line (Point 0 100 0) (Vector 1 (-1) 1) |
| 16 | + Plane] |
| 17 | + let myPlot = dumbRender maxX maxY objects |
| 18 | + let rim = generateImage (\x y -> let (r, g, b) = myPlot !! (x+(maxY-y-1)*maxX) in PixelRGB8 r g b) maxX maxY |
| 19 | + -- \let rim = generateFoldImage (\acc -> \x -> \y -> (acc+1, PixelRGB8 (acc `mod` 255) ( 255) (255))) 0 640 480 |
| 20 | + writePng "test.png" rim |
| 21 | + putStrLn "hello world" |
| 22 | + |
| 23 | +enumCoord :: [a] -> [((Int, Int), a)] |
| 24 | +enumCoord [] = [] |
| 25 | +enumCoord xs = let coords = map toCoord (fst <$> zip [0..] xs) |
| 26 | + in zip coords xs |
| 27 | + where toCoord num = swap $ num `divMod` maxX |
| 28 | + |
| 29 | +type MyPixel = (Word8, Word8, Word8) |
| 30 | +class Object a where |
| 31 | + draw :: a -> [MyPixel] -> [MyPixel] |
| 32 | + |
| 33 | + |
| 34 | +data Point = Point Int Int Int deriving Show |
| 35 | +data Vector = Vector Int Int Int deriving Show |
| 36 | +data Line = Line Point Vector deriving Show |
| 37 | + |
| 38 | +instance Object Line where |
| 39 | + draw line plot = |
| 40 | + snd . markWhite <$> enumCoord plot |
| 41 | + where |
| 42 | + markWhite :: ((Int, Int), MyPixel) -> ((Int, Int), MyPixel) |
| 43 | + markWhite (screenPoint, pixel) = (screenPoint, newPixel) |
| 44 | + where newPixel = if visibleLine screenPoint line then whitePixel else pixel |
| 45 | + whitePixel = (200, 200, 200) |
| 46 | + visibleLine :: (Int, Int) -> Line -> Bool |
| 47 | + visibleLine (sx, sy) (Line (Point x y z) (Vector vx vy vz)) = isTooFar lineY lineZ sy |
| 48 | + where deltaK = fromIntegral (sx - x) / fromIntegral vx |
| 49 | + lineY = deltaK * fromIntegral vy + fromIntegral y |
| 50 | + lineZ = deltaK * fromIntegral vz + fromIntegral z |
| 51 | + isTooFar ly lz sy = ((abs . sqrt) $ (fromIntegral sy - ly) ** 2 + lz ** 2) < 30 |
| 52 | + |
| 53 | +data Plane = Plane |
| 54 | +cameraPos = Point (maxX `div` 2) (maxY * 3 `div` 4) maxX |
| 55 | +screenPointToPlanePoint :: (Int, Int) -> Maybe Point |
| 56 | +screenPointToPlanePoint (sx, sy) = |
| 57 | + let (Point cx cy cz) = cameraPos |
| 58 | + -- plane at y=0 allows to simplify equasion |
| 59 | + steps = (fromIntegral cy) / (fromIntegral $ cy - sy) |
| 60 | + z = (floor $ steps * (fromIntegral cz)) - cz |
| 61 | + x = (floor $ steps * (fromIntegral $ cx - sx)) - cx |
| 62 | + in |
| 63 | + if sy >= cy |
| 64 | + then Nothing -- parallel to plane or diverging |
| 65 | + else Just $ Point x 0 z |
| 66 | + |
| 67 | + |
| 68 | +instance Object Plane where |
| 69 | + draw plane plot = |
| 70 | + snd . markPlane <$> enumCoord plot |
| 71 | + where |
| 72 | + markPlane :: ((Int, Int), MyPixel) -> ((Int, Int), MyPixel) |
| 73 | + markPlane (screenPoint, pixel) = (screenPoint, newPixel) |
| 74 | + where newPixel = lineToPixel screenPoint Plane |
| 75 | + whitePixel = (200, 200, 200) |
| 76 | + blackPixel = (100, 100, 100) |
| 77 | + isBlack (Point x _ z) = mx < squareSize && mz < squareSize || mx >= squareSize && mz >= squareSize |
| 78 | + where mx = x `mod` (squareSize * 2) |
| 79 | + mz = z `mod` (squareSize * 2) |
| 80 | + squareSize = 100 |
| 81 | + lineToPixel :: (Int, Int) -> Plane -> MyPixel |
| 82 | + lineToPixel screenPoint _ = case screenPointToPlanePoint screenPoint of Nothing -> pixel |
| 83 | + (Just plx) -> if isBlack plx then blackPixel else whitePixel |
| 84 | + |
| 85 | +type Render a = Int -> Int -> [a] -> [MyPixel] |
| 86 | + |
| 87 | +dumbRender :: Object a => Render a |
| 88 | +dumbRender x y = foldr draw emptyPlot |
| 89 | + where emptyPlot = replicate (x*y) (0, 0, 0) |
0 commit comments