球との交差
球との交差まで実装。
ようやくコーディングが軌道に乗ってきた。
haskellの難しさというか取っ付きの悪さはシンタックスの問題だと思った。
関数型がどうとかモナドが云々という段階に達する前に
括弧の要不要、','の要不要、type constructorとdata constructorの違いとか
が分かっていないと単純にエラーが出まくってコンパイル通らない。
しかも慣れていないのでエラーから有効な情報を読み取れない。
慣れるしか無いのだが, 結構苦しんだ。
-- ray tracing main=writeP3 "tmp.ppm" width height (rendering width height) where width=256 height=256 -- write raw data to file as PPM text format. writeP3::String->Int->Int->[RGB24]->IO() writeP3 filepath width height rawdata= writeFile filepath ("P3\n" ++(show width)++" "++(show height)++"\n" ++"255\n" ++(unlines $ map show rawdata)) -- Vector instance (Num t)=>Num ([t]) where (+) lhs rhs=[x+y|(x, y)<-(zip lhs rhs)] (-) lhs rhs=[x-y|(x, y)<-(zip lhs rhs)] -- dot product(inner product) dot::Num t=>[t]->[t]->t dot lhs rhs=foldr (+) 0 [x*y|(x,y)<-zip lhs rhs] -- sqareNorm(length * length) sqareNorm::(Num t)=>[t]->t sqareNorm vector=dot vector vector -- norm(length) norm::(Floating f)=>[f]->f norm vector=sqrt $ sqareNorm vector -- rendering rendering::Int->Int->[RGB24] rendering width height=[shading $ intersect sphere $ getRay (x, y) |x<-[0..width-1], y<-[0..height-1]] -- each pixel -- scene where sphere=Sphere [128, 128, 0] 100 -- get color shading::Maybe Sphere->RGB24 shading intersection= case (intersection) of Just sphere -> RGB24 0 0 255 Nothing -> RGB24 255 255 255 -- get ray from pixel coordinate getRay::(Int, Int)->Ray getRay (x, y)=Ray origin direction directionInverse where origin=[(fromIntegral x), (fromIntegral y), 0] direction=[0, 0, -1] directionInverse=[0, 0, -1] -- test intersection intersect::Sphere->Ray->Maybe Sphere intersect sphere@(Sphere center radius) (Ray origin direction directionInverse) | discriminant<0 = Nothing | otherwise = Just sphere where centerToOrigin=origin-center a=(dot direction direction) b=2*(dot direction centerToOrigin) c=(dot centerToOrigin centerToOrigin)-(radius*radius) discriminant=(b*b)-(4*a*c) -- rendering data Ray=Ray [Float] -- origin [Float] -- direction [Float] -- direction inverse each value instance Show Ray where show (Ray origin direction directionInverse)= "origin: "++(show origin)++", direction: "++(show direction)++", inverse: "++(show directionInverse) data Sphere=Sphere [Float] -- center Float -- radius data RGB24=RGB24 Int -- r Int -- g Int -- b instance Show RGB24 where show (RGB24 r g b)=(show r)++" "++(show g)++" "++(show b)
しかし、さすがhaskell。特に行数を節約したわけでも(むしろ無駄遣いしている)
ないのに100行足らずで書けるのは表現力の高さあらわしているか。
Vectorをどうするかについては少し試行錯誤したのだが、
新しい型を導入するよりはlistに必用な演算を定義するだけの方が楽(パターンマッチで中身を取り出す必用が無い)
になりそうなのでそのようにした。
あとtype constructorとdata constructorの名前を変える(type constructorの頭にTをつける)という
命名規則を一瞬取り入れたがださいので止めた。
ただ、はじめは別名をつけて意識的に区別するのもよいかもしれない。
しかし、もう慣れた。多分。
没案↓
data Vector3=Vector3 Float -- x Float -- y Float -- z
要素を取り出すのが面倒くさい。
ラベル付きフィールドは自動定義のセレクタ関数の重複のことを考えると使う気がしなくなった。
newtype (Num t)=>Vector t= Vector [t]
数値のリストをベクトルと見なすというなんとなく数学的な定義を直接表現できていてよいかと思ったのだが、
これを使うところそれぞれでいちいち(Num t)というclass型を書かないといけなくなって面倒なことに。
class typeはなんかc++のテンプレートに似ているかも。
genericにしようとして面倒になるところを含めて。
メモ
instance使用時の"Could not deduce..."エラーの解決で助けになった。
http://itpro.nikkeibp.co.jp/article/COLUMN/20071204/288630/