diff --git a/3dmodels.cabal b/3dmodels.cabal
new file mode 100644
--- /dev/null
+++ b/3dmodels.cabal
@@ -0,0 +1,30 @@
+-- Initial 3dmodels.cabal generated by cabal init.  For further 
+-- documentation, see http://haskell.org/cabal/users-guide/
+
+name:                3dmodels
+version:             0.3.0
+synopsis:            3D model parsers
+description:         3D model parsers
+homepage:            https://github.com/capsjac/3dmodels
+license:             LGPL-3
+license-file:        LICENSE
+author:              capsjac <capsjac at gmail dot com>
+maintainer:          capsjac <capsjac at gmail dot com>
+copyright:           (c) 2014 capsjac
+category:            Graphics
+build-type:          Simple
+extra-source-files:  ChangeLog
+cabal-version:       >=1.10
+extra-source-files:  
+
+source-repository head
+  type:              git
+  location:          https://github.com/capsjac/3dmodels
+
+library
+  exposed-modules:     Graphics.Model.MikuMikuDance, Graphics.Model.DirectX, Graphics.Model.Obj, Graphics.Model.MikuMikuDance.Loader, Graphics.Model.MikuMikuDance.Types
+  -- other-modules:       
+  other-extensions:    OverloadedStrings, RecordWildCards, TupleSections
+  build-depends:       base >=4.7 && <4.8, attoparsec >=0.12 && <0.13, bytestring >=0.10 && <0.11, linear >=1.10 && <1.11, packer >=0.1 && <0.2
+  -- hs-source-dirs:      
+  default-language:    Haskell2010
diff --git a/ChangeLog b/ChangeLog
new file mode 100644
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,8 @@
+Year 2014
+	Aug 20, 15:40 Project start.
+	Aug 21, 16:48 PMX 2.0 finished.
+	        20:48 VMD finished.
+	Aug 22, 21:16 PMD finished. -> 0.1.0.0
+	Aug 23, 14:54 PMX 2.1 supported. -> 0.2.0.0
+	Nov  8,  1:20 Refactored with linear, packer -> 0.3.0
+	
diff --git a/Graphics/Model/DirectX.hs b/Graphics/Model/DirectX.hs
new file mode 100644
--- /dev/null
+++ b/Graphics/Model/DirectX.hs
@@ -0,0 +1,186 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards #-}
+-- | DirectX Object File (.x) Parser
+module Graphics.Model.DirectX (
+  loadXOF,
+  parseXOF,
+  reverseHand,
+
+  XOF(..),
+  DxMesh(..),
+  DxMaterial(..)
+  ) where
+
+import Control.Applicative
+import Data.Attoparsec.ByteString.Char8 as A
+import qualified Data.ByteString as B
+import Data.Traversable
+import Data.Word
+import Linear
+
+-- | DirectX Object File
+data XOF = XOF
+	{ _xofFormat :: B.ByteString
+	, _xofTemplates :: [(B.ByteString, [B.ByteString])]
+	, _xofHeader :: (Word16, Word16, Word32) -- ^ major minor flags
+	, _xofMesh :: DxMesh
+	-- , _xofFrames :: [(frame_name, [(mesh_name, DxMesh)])]
+	-- , _xofAnimationSets :: [DxAnimationSet :: (frame_name, [DxKeyframe])]
+	} deriving Show
+
+data DxMesh = DxMesh
+	{ _dxVertices :: [V3 Float]
+	, _dxFaces :: [Either (V4 Word32) (V3 Word32)]
+	, _dxIndexes :: [Word32]
+	, _dxMaterials :: [DxMaterial]
+	, _dxNormals :: [V3 Float]
+	, _dxNormalIndexes :: [Either (V4 Word32) (V3 Word32)]
+	, _dxTexCoords :: [V2 Float]
+	, _dxVertexColors ::  [(Word32, V4 Float)]
+	} deriving Show
+
+data DxMaterial = DxMaterial
+	{ _dxFaceColor :: V4 Float
+	, _dxPower :: Float
+	, _dxSpecular :: V3 Float
+	, _dxEmissive :: V3 Float
+	, _dxTexturePath :: Maybe B.ByteString
+	} deriving Show
+
+--data DxKeyframe =
+--	  DxRotate Int Quaternion
+--	| DxScale Int (V3 Float)
+--	| DxTranslate Int (V3 Float)
+--	| DxApplyMatrix Int M44 Float
+
+
+loadXOF :: FilePath -> IO XOF
+loadXOF path = parseXOF <$> B.readFile path
+
+parseXOF :: B.ByteString -> XOF
+parseXOF bs =
+	case feed (parse parseXMesh bs) "" of
+		Done _ result -> result
+		result -> error $ "parseXOF: Invalid format:\n" ++ show result
+
+-- | Convert to right handed system.
+reverseHand :: XOF -> XOF
+reverseHand xof@XOF{_xofMesh=DxMesh{..}, ..} =
+	xof { _xofMesh = (_xofMesh xof) {
+		_dxVertices = fmap rhVtx _dxVertices,
+		_dxFaces = fmap rhFace _dxFaces,
+		_dxNormals = fmap rhVtx _dxNormals,
+		_dxNormalIndexes = fmap rhFace _dxNormalIndexes
+	}}
+	where rhVtx (V3 x y z) = V3 x y (-z)
+	      rhFace (Right (V3 a b c)) = Right $ V3 b a c
+	      rhFace (Left (V4 a b c d)) = Left $ V4 a d c b
+
+parseXMesh :: Parser XOF
+parseXMesh = XOF
+	<$> string "xof 0302txt 0064" <* skipSpace -- \r\n
+	<*> many (templateDecl <* skipSpace)
+	<*> headerDecl <* skipSpace
+	<*> meshDecl
+
+takeIdent :: Parser B.ByteString
+takeIdent = A.takeWhile (inClass "a-zA-Z0-9_")
+
+-- | e.g. template Header { field1; field2; ... }
+templateDecl = try $
+	(,) <$> (string "template" >> skipSpace >> takeIdent <* skipSpace) -- name
+	<*> braces '{' '}' (skipSpace >> many (fieldDecl <* skipSpace)) -- fields
+
+fieldDecl =
+	braces '<' '>' (A.takeWhile (/= '>'))
+	<|> braces '[' ']' (string "..." <|> takeIdent)
+	<|> A.takeWhile (inClass "a-zA-Z0-9_ []") <* char ';'
+
+float = realToFrac <$> signed double
+
+getVector :: (Applicative a, Traversable a) => Parser (a Float)
+getVector = sequenceA $ pure getFloatField
+
+-- | e.g. <aaa> => braces '<' '>' (string "aaa")
+braces l r p = char l *> p <* char r
+
+semicolon = char ';' <* skipSpace
+comma = char ','
+maybeComma = (comma <|> return ' ') >> skipSpace
+
+getIntField = decimal <* semicolon
+getFloatField = float <* semicolon
+getStringField = braces '"' '"' (A.takeWhile (/= '"')) <* semicolon
+
+getVectorField :: (Applicative a, Traversable a) => Parser (a Float)
+getVectorField = getVector <* semicolon
+
+getArray :: Parser a -> Int -> Parser [a]
+getArray parser len = count len (parser <* maybeComma) <* semicolon
+
+section name p = do
+	string name
+	skipSpace
+	braces '{' '}' (skipSpace >> p)
+
+headerDecl :: Parser (Word16, Word16, Word32)
+headerDecl = section "Header" $ do
+	major <- fromIntegral <$> getIntField
+	minor <- fromIntegral <$> getIntField
+	flags <- fromIntegral <$> getIntField
+	return (major, minor, flags)
+
+meshDecl :: Parser DxMesh
+meshDecl = section "Mesh" $ do
+	vertices <- getIntField >>= getArray getVector
+	faces <- getIntField >>= getArray meshFace
+	(indexes, materials) <- option ([], []) meshMaterialList <* skipSpace
+	(normals, faceNormals) <- option ([], []) meshNormals <* skipSpace
+	DxMesh vertices faces indexes materials normals faceNormals
+		<$> option [] meshTextureCoords <* skipSpace
+		<*> option [] meshVertexColors <* skipSpace
+
+meshFace = fmap Left meshFace4 <|> fmap Right meshFace3
+meshFace4 = do
+	char '4' >> semicolon
+	sequenceA $ V4 (decimal <* comma) (decimal <* comma)
+		(decimal <* comma) (decimal <* char ';')
+meshFace3 = do
+	char '3' >> semicolon
+	sequenceA $ V3 (decimal <* comma)
+		(decimal <* comma) (decimal <* char ';')
+
+meshMaterialList = section "MeshMaterialList" $ do
+	nMaterials <- getIntField
+	indexes <- getIntField >>= getArray faceIndex
+	semicolon
+	materials <- count nMaterials (materialDecl <* skipSpace)
+	return (indexes, materials)
+
+faceIndex :: Parser Word32
+faceIndex = decimal
+
+materialDecl = section "Material" $ do
+	faceColor <- getVectorField
+	power <- getFloatField
+	specularColor <- getVectorField
+	emissiveColor <- getVectorField
+	texFile <- optional textureFilename
+	skipSpace
+	return $ DxMaterial faceColor power specularColor emissiveColor texFile
+
+textureFilename = section "TextureFilename" getStringField
+
+meshNormals = section "MeshNormals" $ do
+	normals <- getIntField >>= getArray getVector
+	faceNormals <- getIntField >>= getArray meshFace
+	return (normals, faceNormals)
+
+meshTextureCoords = section "MeshTextureCoords" $
+	getIntField >>= getArray getVector
+
+meshVertexColors = section "MeshVertexColors" $
+	getIntField >>= getArray indexedColor
+
+indexedColor = (,) <$> fmap fromIntegral getIntField <*> getVector
+
diff --git a/Graphics/Model/MikuMikuDance.hs b/Graphics/Model/MikuMikuDance.hs
new file mode 100644
--- /dev/null
+++ b/Graphics/Model/MikuMikuDance.hs
@@ -0,0 +1,19 @@
+-- | MikuMikuDance (MMD) model/motion reader.
+-- This module provides unpacking interfaces for MMD 3D models and their motions,
+-- both traditional PMD and newer PMX format are fully supported.
+-- 
+-- > model <- loadMMD "博麗霊夢_n＋式/博麗霊夢_n＋式.pmd" :: IO MMD
+-- 
+-- > motion <- loadVMD "Motion_BadApple/BadApple_Rside_20110206.vmd" :: IO VMD
+module Graphics.Model.MikuMikuDance (
+  loadMMD,
+  decodeMMD,
+  unpackMMD,
+  loadVMD,
+  decodeVMD,
+  unpackVMD,
+  module Graphics.Model.MikuMikuDance.Types
+) where
+import Graphics.Model.MikuMikuDance.Types
+import Graphics.Model.MikuMikuDance.Loader
+
diff --git a/Graphics/Model/MikuMikuDance/Loader.hs b/Graphics/Model/MikuMikuDance/Loader.hs
new file mode 100644
--- /dev/null
+++ b/Graphics/Model/MikuMikuDance/Loader.hs
@@ -0,0 +1,530 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Graphics.Model.MikuMikuDance.Loader where
+import Control.Applicative
+import Control.Monad
+import Data.Int
+import Data.Bits
+import Data.Packer
+import qualified Data.ByteString as B
+import Graphics.Model.MikuMikuDance.Types
+import Linear
+import Unsafe.Coerce
+
+
+getFloat :: Unpacking Float -- Performance hack (may not work on your devices!)
+getFloat = unsafeCoerce <$> getWord32LE
+
+get2 :: Unpacking a -> Unpacking (V2 a)
+get2 get = V2 <$> get <*> get
+
+get3 :: Unpacking a -> Unpacking (V3 a)
+get3 get = V3 <$> get <*> get <*> get
+
+get4 :: Unpacking a -> Unpacking (V4 a)
+get4 get = V4 <$> get <*> get <*> get <*> get
+
+getV2 :: Unpacking (V2 Float)
+getV2 = get2 getFloat
+
+getV3 :: Unpacking (V3 Float)
+getV3 = get3 getFloat
+
+getV4 :: Unpacking (V4 Float)
+getV4 = get4 getFloat
+
+getWord8Int, getWord16Int, getWord32Int :: Unpacking Int
+getWord8Int = fromIntegral <$> getWord8
+getWord16Int = fromIntegral <$> getWord16LE
+getWord32Int = fromIntegral <$> getWord32LE
+
+-- Left handed system -> Right handed system
+ltrTrans (V3 x y z) = V3 x y (-z)
+ltrEular (V3 x y z) = V3 (-x) (-y) z
+ltrQuat (V4 t i j k) = V4 (-t) i j k
+getPos = fmap ltrTrans getV3
+getEular = fmap ltrEular getV3
+getQuat = fmap ltrQuat getV4
+
+
+-- * PMX
+
+loadMMD :: FilePath -> IO MMD 
+loadMMD path = decodeMMD <$> B.readFile path
+
+decodeMMD :: B.ByteString -> MMD
+decodeMMD bs = runUnpacking unpackMMD bs
+
+unpackMMD :: Unpacking MMD
+unpackMMD = do
+	magic <- getWord32BE
+	case magic of
+		0x506d6400 -> unpackPMD
+		0x504d5820 -> unpackPMX
+		_ -> fail "Not a MMD Model"
+
+getUnsignedIndexUnpacker :: Unpacking (Unpacking Int32)
+getUnsignedIndexUnpacker = do
+	size <- getWord8
+	return $ case size of
+		1 -> fromIntegral <$> getWord8
+		2 -> fromIntegral <$> getWord16LE
+		4 -> fromIntegral <$> getWord32LE
+
+getIndexUnpacker :: Unpacking (Unpacking Int32)
+getIndexUnpacker = do
+	size <- getWord8
+	return $ case size of
+		1 -> ((fromIntegral :: Int8 -> Int32) . fromIntegral) <$> getWord8
+		2 -> ((fromIntegral :: Int16 -> Int32) . fromIntegral) <$> getWord16LE
+		4 -> fromIntegral <$> getWord32LE
+
+getTextBuf :: Unpacking B.ByteString
+getTextBuf = getWord32Int >>= getBytesCopy
+
+-- Polygon Model Extended
+unpackPMX :: Unpacking MMD
+unpackPMX = do
+	version <- getFloat
+	size <- getWord8
+	when ((version /= 2.0 && version /= 2.1) || size /= 8) $
+		fail "Not PMX 2.0/2.1"
+	
+	-- PMX Header
+	-- 0: UTF-16, 1: UTF-8
+	isUTF16 <- getWord8
+	let charset 0 = "UTF-16"
+	    charset 1 = "UTF-8"
+	numExtUVs <- getWord8
+	getVertexIndex <- getUnsignedIndexUnpacker
+	getTextureIndex <- getIndexUnpacker
+	getMaterialndex <- getIndexUnpacker
+	getBoneIndex <- getIndexUnpacker
+	getMorphIndex <- getIndexUnpacker
+	getBodyIndex <- getIndexUnpacker
+	modelName <- getTextBuf
+	modelNameEn <- getTextBuf
+	comment <- getTextBuf
+	commentEn <- getTextBuf
+
+	-- Vertex
+	numVertices <- getWord32Int
+	vertices <- replicateM numVertices $ do
+		pos <- getPos
+		normal <- getPos
+		uv <- getV2
+		extV4s <- replicateM (fromIntegral numExtUVs) getV4
+		weightFormat <- getWord8
+		w <- case weightFormat of
+			0 -> BDEF1 <$> getBoneIndex
+			1 -> BDEF2 <$> get2 getBoneIndex <*> getFloat
+			2 -> BDEF4 <$> get4 getBoneIndex <*> getV4
+			-- SDEF need left<->right handed system transformation?
+			3 -> SDEF <$> get2 getBoneIndex <*> getFloat <*> get3 getPos
+			4 -> QDEF <$> get4 getBoneIndex <*> getV4
+		-- edgeScalingFactor
+		esf <- getFloat
+		return $ MMDVertex pos normal uv extV4s w esf
+
+	-- Plane
+	numPlanes <- getWord32Int
+	planes <- replicateM (numPlanes `div` 3) $ do
+		V3 p q r <- get3 getVertexIndex
+		return $ V3 q p r -- from left to right
+	
+	-- Texture
+	numTextures <- getWord32Int
+	textures <- replicateM numTextures getTextBuf
+	
+	-- Material
+	numMaterials <- getWord32Int
+	materials <- replicateM numMaterials $ do
+		name <- getTextBuf
+		nameEn <- getTextBuf
+		diffuse <- getV4
+		specular <- getV3
+		shininess <- getFloat
+		ambient <- getV3
+		renderFlags <- getWord8
+		edgeColor <- getV4
+		edgeSize <- getFloat
+		tex <- getTextureIndex
+		sphereTex <- getTextureIndex
+		sphereMode <- getWord8
+		toonFlag <- getWord8
+		toon <- case toonFlag of
+			0 -> getTextureIndex
+			1 -> (\x -> fromIntegral x - 10) <$> getWord8
+		script <- getTextBuf
+		verticesCount <- getWord32LE
+		return $ MMDMaterial name nameEn diffuse specular shininess ambient
+			renderFlags edgeColor edgeSize tex sphereTex sphereMode toon
+			script verticesCount
+
+	-- Bone
+	numBones <- getWord32Int
+	bones <- replicateM numBones $ do
+		name <- getTextBuf
+		nameEn <- getTextBuf
+		pos <- getPos
+		parent <- getBoneIndex
+		transDepth <- getWord32Int
+		flags <- getWord16LE
+		link <- if flags .&. 1 == 0
+			then Left <$> getV3
+			else Right <$> getBoneIndex
+		following <- if flags .&. 0x300 /= 0
+			then Just <$> ((,) <$> getBoneIndex <*> getFloat)
+			else return Nothing
+		axis <- if flags .&. 0x400 /= 0
+			then Just <$> getV3
+			else return Nothing
+		local <- if flags .&. 0x800 /= 0
+			then Just <$> ((,) <$> getPos <*> getPos)
+			else return Nothing
+		external <- if flags .&. 0x2000 /= 0
+			then Just <$> getWord32Int
+			else return Nothing
+		ik <- if flags .&. 0x20 /= 0
+			then do
+				ix <- getBoneIndex
+				lc <- getWord32Int
+				maxRot <- getFloat
+				numlink <- getWord32Int
+				links <- replicateM numlink $ do
+					ix <- getBoneIndex
+					ranged <- getWord8
+					limit <- if ranged == 1
+						then Just <$> ((,) <$> getEular <*> getEular)
+						else return Nothing
+					return (ix, limit)
+				return . Just $ MMDIK ix lc maxRot links
+			else return Nothing
+		return $ MMDBone name nameEn pos parent transDepth flags link
+			following axis local external ik
+
+	-- Morph
+	numMorphs <- getWord32Int
+	morphs <- replicateM numMorphs $ do
+		name <- getTextBuf
+		nameEn <- getTextBuf
+		facial <- getWord8
+		morphType <- getWord8
+		let getMorphOffset = case morphType of
+			0 -> GroupMorph <$> getMorphIndex <*> getFloat
+			1 -> VertexMorph <$> getVertexIndex <*> getPos
+			2 -> BoneMorph <$> getBoneIndex <*> getPos <*> getQuat
+			3 -> UVMorph <$> getVertexIndex <*> getV4
+			4 -> UV1Morph <$> getVertexIndex <*> getV4
+			5 -> UV2Morph <$> getVertexIndex <*> getV4
+			6 -> UV3Morph <$> getVertexIndex <*> getV4
+			7 -> UV4Morph <$> getVertexIndex <*> getV4
+			8 -> MaterialMorph <$> getMaterialndex <*> getWord8
+				<*> getV4 <*> getV3 <*> getFloat
+				<*> getV3 <*> getV4 <*> getFloat
+				<*> getV4 <*> getV4 <*> getV4
+			9 -> FlipMorph <$> getMorphIndex <*> getFloat
+			10 -> ImpulseMorph <$> getBodyIndex <*> ((/= 0) <$> getWord8)
+				<*> getPos <*> getEular
+		numOffsets <- getWord32Int
+		offsets <- replicateM numOffsets getMorphOffset
+		return $ MMDMorph name nameEn facial offsets
+
+	-- Group
+	numGroups <- getWord32Int
+	groups <- replicateM numGroups $ do
+		name <- getTextBuf
+		nameEn <- getTextBuf
+		isSpecial <- getWord8
+		count <- getWord32Int
+		elements <- replicateM count $ do
+			kind <- getWord8
+			case kind of
+				0 -> Left <$> getBoneIndex
+				1 -> Right <$> getMorphIndex
+		return $ MMDGroup name nameEn (isSpecial /= 0) elements
+
+	-- Rigid Body
+	numRigidBodies <- getWord32Int
+	bodies <- replicateM numRigidBodies $
+		MMDRigidBody <$> getTextBuf <*> getTextBuf <*> getBoneIndex
+			<*> getWord8 <*> getWord16LE <*> getWord8
+			<*> getV3 <*> getPos <*> getEular
+			<*> getFloat <*> getFloat <*> getFloat
+			<*> getFloat <*> getFloat <*> getWord8
+
+	-- Joint
+	numJoints <- getWord32Int
+	joints <- replicateM numJoints $
+		MMDJoint <$> getTextBuf <*> getTextBuf <*> getWord8
+			<*> getBodyIndex <*> getBodyIndex
+			<*> getPos <*> getEular
+			<*> getPos <*> getPos <*> getEular <*> getEular
+			<*> getPos <*> getEular
+
+	-- Soft Body
+	numSoftBodies <- getWord32Int
+	softBodies <- replicateM numSoftBodies $
+		MMDSoftBody <$> getTextBuf <*> getTextBuf <*> getWord8
+			<*> getMaterialndex <*> getWord8 <*> getWord16LE
+			<*> getWord8 <*> getWord32Int <*> getWord32Int
+			<*> getFloat <*> getFloat <*> getWord32Int
+			<*> get3 getV4 <*> get2 getV3
+			<*> get4 getWord32Int <*> getV3
+			<*> (getWord32Int >>= flip replicateM (
+				(,,) <$> getBodyIndex <*> getVertexIndex
+					<*> ((== 1) <$> getWord8)))
+			<*> (getWord32Int >>= flip replicateM getVertexIndex)
+
+	-- Sums up!
+	return $ MMD version (charset isUTF16) numExtUVs
+		modelName modelNameEn comment commentEn
+		vertices planes textures [] materials bones morphs groups bodies
+		joints softBodies
+
+-- get fixed length C string terminating NUL byte
+getFixedStr :: Int -> Unpacking B.ByteString
+getFixedStr l = B.takeWhile (/= 0) <$> getBytesCopy l
+
+-- Polygon Model Data
+unpackPMD :: Unpacking MMD
+unpackPMD = do
+	unpackSetPosition 3
+	version <- getFloat
+	when (version /= 1.0) $
+		fail "Not PMD 1.0"
+	
+	-- PMD Header
+	name <- getFixedStr 20
+	comment <- getFixedStr 256
+	
+	-- Vertex
+	numVertices <- getWord32Int
+	vertices <- replicateM numVertices $ do
+		v <- MMDVertex <$> getPos <*> getPos <*> getV2
+		weight <- BDEF2 <$> get2 getBoneIndex <*> getPercent
+		edgeFlag <- getWord8
+		let edgeScale = fromIntegral (1 - edgeFlag)
+		return $ v [] weight edgeScale
+
+	-- Plane
+	numPlanesX3 <- getWord32Int
+	planes <- replicateM (numPlanesX3 `div` 3) $ do
+		V3 p q r <- get3 getVertexIndex
+		return $ V3 q p r
+
+	-- Material
+	numMaterials <- getWord32Int
+	materials <- forM [0 .. numMaterials - 1] $ \i -> do
+		diffuse <- getV4
+		specularity <- getFloat
+		specular <- getV3
+		ambient <- getV3
+		toonIx <- fromIntegral <$> getWord8 -- -1:0.bmp, 0..9:[1..10].bmp
+		edgeFlag <- getWord8 -- 1 to disable
+		numVertices <- getWord32LE
+		texturePath <- getFixedStr 20
+		let sphereMode =
+			-- "tex.bmp*sphere.sph" => Multiply (MMD 5.12-)
+			-- "tex.bmp*sphere.spa" => Plus (MMD 5.12-)
+			-- "tex.bmp/sphere.sph" => Multiply (MMD 5.11)
+			-- "tex.bmp" or "sphere.sph" => None (MMD 5.09-)
+			if 42 `B.elem` texturePath -- or 47 `elem` texturePath
+			then if ".spa" `B.isSuffixOf` texturePath
+				then 2 -- Add
+				else 1 -- Mul
+			else 0 -- None
+		let i' = fromIntegral i
+		return $ MMDMaterial texturePath ""
+			diffuse specular specularity ambient
+			((1 - edgeFlag) * 16) -- draw edge
+			(V4 0 0 0 1) 1.0 (i' * 2) (i' * 2 + 1) sphereMode
+			(toonIx - 10) B.empty numVertices
+
+	-- Texture
+	let split x y | mSphereMode x == 0 = mNameJa x : mNameJa x : y
+	    split x y = t1 : B.tail t2 : y
+	    	where (t1,t2) = B.break (== 42) (mNameJa x)
+	let textures = foldr split [] materials
+	
+	-- Bone
+	numBones <- getWord16Int
+	bones <- replicateM numBones $ do
+		name <- getFixedStr 20
+		parent <- getBoneIndex
+		tailBone <- getBoneIndex
+		-- 0:回転 1:回転と移動 2:IK 3:不明 4:IK影響下
+		-- 5:回転影響下 6:IK接続先 7:非表示 8:捻り 9:回転運動
+		typ <- getWord8
+		ik <- getBoneIndex
+		headPos <- getPos
+		return $ MMDBone name "" headPos parent
+			0 0 -- XXX ?
+			(Right 0) Nothing --XXX follow link
+			Nothing Nothing Nothing Nothing
+
+	-- IK
+	numIKBones <- getWord16Int
+	ikbones <- replicateM numIKBones $ do
+		bone <- getBoneIndex
+		target <- getBoneIndex
+		ikLen <- getWord8Int
+		iter <- getWord16Int
+		rotLimit <- (* 4) <$> getFloat
+		links <- replicateM ikLen $
+			(\x -> (x, Nothing)) <$> getBoneIndex
+		return $ (bone, MMDIK target iter rotLimit links)
+	let bones' = zipWith (\ i bone ->
+		bone { bIK = lookup i ikbones }) [0..] bones
+
+	-- Face
+	numFaces <- getWord16Int
+	faces <- replicateM numFaces $ do
+		name <- getFixedStr 20
+		numVertices <- getWord32Int
+		facial <- getWord8
+		-- 0:Base, 1:Brow, 2: Eye, 3: Lip, 4:Other
+		-- XXX 'base' should be ignored (0,0,0)???
+		vMorph <- replicateM numVertices $
+			VertexMorph <$> fmap fromIntegral getWord32LE <*> getPos
+		return $ MMDMorph name "" facial vMorph
+		
+	faceListLen <- getWord8Int
+	faceList <- replicateM faceListLen getMorphIndex
+
+	-- Group
+	boneGroupsLen <- getWord8Int
+	boneGroups <- replicateM boneGroupsLen (getFixedStr 50)
+	let bGsEn = replicate boneGroupsLen ""
+	numGroupedBones <- getWord32Int
+	groupedBones <- replicateM numGroupedBones $
+		(,) <$> getBoneIndex <*> getWord8
+
+	-- Ext - English support since MMD 4.03
+	empty <- isEmpty
+	haveEnNames <- if empty then return False else (== 1) <$> getWord8
+	(nameEn, commentEn, bones, faces, bGsEn) <-
+		if not haveEnNames
+		then return ("", "", bones, faces, bGsEn)
+		else do
+			name' <- getFixedStr 20
+			comment' <- getFixedStr 256
+			bones' <- forM bones $ \bone -> do
+					nameEn <- getFixedStr 20
+					return bone { bName = nameEn }
+			faces' <- forM faces $ \morph -> do
+					nameEn <- getFixedStr 20
+					return morph { mphName = nameEn }
+			bGsEn' <- replicateM (boneGroupsLen - 1) (getFixedStr 50)
+			return (name', comment', bones', faces', bGsEn')
+
+	-- UTF-16
+	let root = "R\NULo\NULo\NULt\NUL"
+	let rootGroup = MMDGroup root root True [Left 0]
+	let faceGroup = MMDGroup "h\136\197`" "E\NULx\NULp\NUL" True
+			(map Right faceList)
+	let groups = rootGroup : faceGroup :
+		zipWith3 (\i j e ->
+			MMDGroup j e False $
+				map (Left . fst) $
+					filter ((== i).snd) groupedBones
+		) [1..] boneGroups bGsEn
+		-- group 0 ('center') is reserved for the root bone
+	
+	-- toon texture list since MMD 4.03
+	toonTexList <- if empty then return []
+		else replicateM 10 (getFixedStr 100)
+
+	-- Ext - Bullet Physics
+	--    Rigid Body
+	numRigidBodies <- getWord32Int
+	bodies <- replicateM numRigidBodies $
+		MMDRigidBody <$> getFixedStr 20 <*> pure "" <*> getBoneIndex
+			<*> getWord8 <*> getWord16LE <*> getWord8
+			<*> getV3 <*> getPos <*> getEular
+			<*> getFloat <*> getFloat <*> getFloat
+			<*> getFloat <*> getFloat <*> getWord8
+	--    Joint
+	numJoints <- getWord32Int
+	joints <- replicateM numJoints $
+		MMDJoint <$> getFixedStr 20 <*> pure "" <*> pure 0
+			<*> getBodyIndex <*> getBodyIndex
+			<*> getPos <*> getEular
+			<*> getPos <*> getPos <*> getEular <*> getEular
+			<*> getPos <*> getEular
+	
+	-- Sums up!
+	return $ MMD 1 "Shift-JIS" 0 name nameEn comment commentEn
+		vertices planes textures toonTexList materials bones'
+		faces groups bodies joints []
+	where
+		toInt16 = fromIntegral
+		toInt32 = fromIntegral :: Int16 -> Int32
+		getBoneIndex = toInt32 . toInt16 <$> getWord16LE
+		getPercent = (/100) . fromIntegral <$> getWord8
+		getVertexIndex = fromIntegral <$> getWord16LE
+		getMorphIndex = getVertexIndex
+		getBodyIndex = fromIntegral <$> getWord32LE
+
+
+-- * VMD
+
+loadVMD :: FilePath -> IO VMD
+loadVMD path = decodeVMD <$> B.readFile path
+
+decodeVMD :: B.ByteString -> VMD
+decodeVMD bs = runUnpacking unpackVMD bs
+
+unpackVMD :: Unpacking VMD
+unpackVMD = do
+	magic <- getBytes 30
+	when (magic /= "Vocaloid Motion Data 0002\0\0\0\0\0") $
+		-- MMD 3.0+ required.
+		fail "Not Vocaloid Motion Data"
+	
+	-- If the content is camera, light, and self shadow,
+	-- model name should be "カメラ・照明\0on Data"
+	name <- getFixedStr 20
+
+	numBoneKeyframes <- getWord32Int
+	boneM <- replicateM numBoneKeyframes $
+		BoneKeyframe <$> getFixedStr 15 <*> getWord32LE <*> getPos
+			<*> getQuat <*> replicateM 64 (fromIntegral <$> getWord8)
+	
+	numMorphKeyframes <- getWord32Int
+	morphM <- replicateM numMorphKeyframes $
+		MorphKeyframe <$> getFixedStr 15 <*> getWord32LE <*> getFloat
+
+	numCameraKeyframes <- getWord32Int
+	cameraM <- replicateM numCameraKeyframes $
+		CameraKeyframe <$> getWord32LE <*> getFloat
+			<*> getPos <*> getEular
+			<*> replicateM 24 (fromIntegral <$> getWord8)
+			<*> getWord32LE <*> ((== 0) <$> getWord8)
+
+	numLightKeyframes <- getWord32Int
+	lightM <- replicateM numLightKeyframes $
+		LightKeyframe <$> getWord32LE <*> getV3 <*> getPos
+
+	-- After MMDv6.19 --
+	empty <- isEmpty
+	shadowM <- if empty then return []
+	else do
+		numSelfShadowKeyframes <- getWord32Int
+		replicateM numSelfShadowKeyframes $
+			ShadowKeyframe <$> getWord32LE
+				<*> (fromIntegral <$> getWord8) <*> getFloat
+	
+	-- Since MMDv7.40 --
+	empty <- isEmpty
+	ctrlM <- if empty then return []
+	else do
+		numCtrlKeyframes <- getWord32Int
+		replicateM numCtrlKeyframes $
+			ControlKeyframe <$> getWord32LE <*> ((== 1) <$> getWord8)
+				<*> (getWord32Int >>= \count ->
+					replicateM count
+					((,) <$> getFixedStr 20 <*> ((== 1) <$> getWord8)))
+
+	-- Sums up!
+	return $ VMD name boneM morphM cameraM lightM shadowM ctrlM
+
diff --git a/Graphics/Model/MikuMikuDance/Types.hs b/Graphics/Model/MikuMikuDance/Types.hs
new file mode 100644
--- /dev/null
+++ b/Graphics/Model/MikuMikuDance/Types.hs
@@ -0,0 +1,306 @@
+module Graphics.Model.MikuMikuDance.Types (
+  VertexIx,
+  TexIx,
+  BoneIx,
+  MorphIx,
+  MaterialIx,
+  BodyIx,
+  MMD(..),
+  MMDVertex(..),
+  MMDWeight(..),
+  MMDMaterial(..),
+  MMDBone(..),
+  MMDIK(..),
+  MMDMorph(..),
+  MMDMorphOffest(..),
+  MMDGroup(..),
+  MMDRigidBody(..),
+  MMDJoint(..),
+  MMDSoftBody(..),
+
+  VMD(..),
+  BoneKeyframe(..),
+  MorphKeyframe(..),
+  CameraKeyframe(..),
+  LightKeyframe(..),
+  ShadowKeyframe(..),
+  ControlKeyframe(..),
+
+  VPD
+  ) where
+import qualified Data.ByteString as B
+import Data.Int
+import Data.Word
+import Linear
+
+
+-- * PMX (Polygon Model eXtended)
+
+type VertexIx = Int32
+type TexIx = Int32
+type BoneIx = Int32
+type MorphIx = Int32
+type MaterialIx = Int32
+type BodyIx = Int32
+
+-- | PMX 2.1 spec made by Kyockhook-P (極北P)
+data MMD = MMD
+	{ mmdFormat :: Float
+	-- ^ 1.0: PMD, 2.0/2.1:PMX
+	, mmdCharCode :: String
+	-- ^ UTF-16, UTF-8, Shift-JIS
+	, mmdExtUVs :: Word8 -- ^ in [0..4]
+	, mmdNameJa :: B.ByteString
+	, mmdName :: B.ByteString
+	, mmdCommentJa :: B.ByteString
+	, mmdComment :: B.ByteString
+	, mmdVertices :: [MMDVertex]
+	, mmdPlanes :: [V3 VertexIx]
+	-- ^ ABC(Triangle), /Since 2.1/: AAA(Point), ABA(Line)
+	, mmdTextures :: [B.ByteString]
+	, mmdAltToonTex :: [B.ByteString]
+	-- ^ length: 0(PMX) or 10(PMD, if exists)
+	, mmdMaterials :: [MMDMaterial]
+	, mmdBones :: [MMDBone]
+	, mmdMorphs :: [MMDMorph]
+	, mmdGroups :: [MMDGroup]
+	, mmdRigidBodies :: [MMDRigidBody]
+	, mmdJoints :: [MMDJoint]
+	, mmdSoftBodies :: [MMDSoftBody]
+	-- ^ /Since 2.1/
+	} deriving Show
+
+data MMDVertex = MMDVertex
+	{ vPosition :: V3 Float
+	, vNormal :: V3 Float
+	, vUV :: V2 Float
+	, vExtUVs :: [V4 Float]
+	, vWeight :: MMDWeight
+	, vEdgeScale :: Float
+	} deriving Show
+
+data MMDWeight =
+	  BDEF1 BoneIx
+	-- | Weight of the first bone
+	| BDEF2 (V2 BoneIx) Float
+	-- | Weight of each bone
+	| BDEF4 (V4 BoneIx) (V4 Float)
+	-- | BDEF2 + SDEF-C(x,y,z) + SDEF-R0(x,y,z) + SDEF-R1(x,y,z)
+	| SDEF (V2 BoneIx) Float (M33 Float)
+	-- | /Since 2.1/ DualQuaternion
+	| QDEF (V4 BoneIx) (V4 Float)
+	deriving Show
+
+data MMDMaterial = MMDMaterial
+	{ mNameJa :: B.ByteString
+	, mName :: B.ByteString
+	, mDiffuse :: V4 Float -- ^ RGBA
+	, mSpecular :: V3 Float -- ^ RGB
+	, mSpecularFactor :: Float
+	, mAmbient :: V3 Float -- ^ RGB
+	-- | 1:cullface=both, 2:drop shadow, 4:render to self shadow map,
+	-- 8:render to self shadow, 16:draw edges,
+	-- /Since 2.1/ => 32:vertex color, 64:As Points, 128:As Lines
+	, mOptions :: Word8
+	, mEdgeColor :: V4 Float -- ^ RGBA
+	-- | /Since 2.1/ Point size (See mOptions)
+	, mEdgeSize :: Float
+	, mTexture :: TexIx
+	, mSphereTexture :: TexIx
+	-- | 0: None, 1: Multiply, 2: Add, 3: Use subtexture
+	, mSphereMode :: Word8
+	-- | Indexed texture >= 0, -10 <= shered toon texture (0-9) - 10 < 0
+	, mToonTexture :: TexIx
+	, mComment :: B.ByteString
+	-- | The number of planes * 3
+	, mNumVertices :: Word32
+	} deriving Show
+
+data MMDBone = MMDBone
+	{ bNameJa :: B.ByteString
+	, bName :: B.ByteString
+	, bPosition :: V3 Float
+	, bParent :: BoneIx
+	, bTransformDepth :: Int
+	, bFlags :: Word16
+	, bLInk :: Either (V3 Float) BoneIx -- ^ offset or bone index
+	, bFollow :: Maybe (BoneIx, Float)
+	, bAxis :: Maybe (V3 Float)
+	, bLocalAxis :: Maybe (V3 Float, V3 Float)
+	, bExternalParent :: Maybe Int
+	, bIK :: Maybe MMDIK
+	} deriving Show
+
+data MMDIK = MMDIK
+	{ ikTarget :: BoneIx
+	, ikLoopCount :: Int
+	, ikRotLimitPerLoop :: Float
+	, ikLinks :: [(BoneIx, Maybe (V3 Float, V3 Float))]
+	} deriving Show
+
+data MMDMorph = MMDMorph
+	{ mphNameJa :: B.ByteString
+	, mphName :: B.ByteString
+	, mphUICtrl :: Word8
+	, mphOffsets :: [MMDMorphOffest]
+	} deriving Show
+
+data MMDMorphOffest =
+	  GroupMorph MorphIx Float
+	| VertexMorph VertexIx (V3 Float) -- ^ offset
+	| BoneMorph BoneIx (V3 Float) (V4 Float)
+	| UVMorph VertexIx (V4 Float)
+	| UV1Morph VertexIx (V4 Float)
+	| UV2Morph VertexIx (V4 Float)
+	| UV3Morph VertexIx (V4 Float)
+	| UV4Morph VertexIx (V4 Float)
+	| MaterialMorph MaterialIx Word8 (V4 Float) (V3 Float) Float (V3 Float) (V4 Float) Float (V4 Float) (V4 Float) (V4 Float)
+	-- ^ 0:*, 1:+. Diffuse, Specular, SpecularFactor, Ambient, EdgeColor, EdgeSize, TextureFactors, SphereTexFactors, ToonTexFactors.
+	| FlipMorph MorphIx Float
+	-- ^ /Since 2.1/
+	| ImpulseMorph BodyIx Bool (V3 Float) (V3 Float)
+	-- ^ /Since 2.1/ isLocal, velocity, torque
+	deriving Show
+
+data MMDGroup = MMDGroup
+	{ gNameJa :: B.ByteString
+	, gName :: B.ByteString
+	, gIsReserved :: Bool
+	, gElements :: [Either BoneIx MorphIx]
+	} deriving Show
+
+data MMDRigidBody = MMDRigidBody
+	{ rbNameJa :: B.ByteString
+	, rbName :: B.ByteString
+	, rbRelated :: BoneIx
+	, rbGroup :: Word8
+	, rbNoCollisionGroup :: Word16
+	, rbShape :: Word8 -- ^ 0:Ball, 1:Box, 2:Capsule
+	, rbSize :: V3 Float -- ^ (x,y,z)
+	, rbPosition :: V3 Float -- ^ (x,y,z)
+	, rbRotation :: V3 Float -- ^ (x,y,z)
+	, rbMass :: Float
+	, rbSpeedAttenuation :: Float
+	, rbRotAttenuation :: Float
+	, rbRepulsion :: Float
+	, rbFriction :: Float
+	, rbComputeMode :: Word8 -- ^ 0: static, 1: dynamic, 2: both
+	} deriving Show
+
+data MMDJoint = MMDJoint
+	{ jNameJa :: B.ByteString
+	, jName :: B.ByteString
+	, jointType :: Word8
+	, jBone1 :: BodyIx
+	, jBone2 :: BodyIx
+	, jPosition :: V3 Float
+	, jRotation :: V3 Float
+	, jPosUpperBound :: V3 Float
+	, jPosLowerBound :: V3 Float
+	, jRotUpperBound :: V3 Float
+	, jRotLowerBound :: V3 Float
+	, jPosSpringConst :: V3 Float
+	, jRotSpringConst :: V3 Float
+	} deriving Show
+
+-- | /Since 2.1/
+data MMDSoftBody = MMDSoftBody
+	{ sbNameJa :: B.ByteString
+	, sbName :: B.ByteString
+	, sbShape :: Word8
+	-- ^ 0:TriMesh, 1:Rope
+	, sbRelated :: MaterialIx
+	, sbGroup :: Word8
+	, sbNoCollisionGroup :: Word16
+	, sbOptions :: Word8
+	-- ^ 1:generateBendingConstraints(B-Link), 2:generateClusters, 4:randomizeConstraints
+	, sbBLinkDistance :: Int
+	, sbNumClusters :: Int
+	, sbMass :: Float
+	, sbCollisionMargin :: Float
+	, sbAeroModel :: Int
+	-- ^ 0:V_Point, 1:V_TwoSided, 2:V_OneSided, 3:F_TwoSided, 4:F_OneSided
+	, sbConfig :: V3 (V4 Float)
+	-- ^ (VCF DP DG LF, PR VC DF MT, CHR KHR SHR AHR)
+	, sbCluster :: V2 (V3 Float)
+	-- ^ (SRHR_CL SKHR_CL SSHR_CL, SR_SPLT_CL SK_SPLT_CL SS_SPLT_CL)
+	, sbIteration :: V4 Int
+	-- ^ V_IT P_IT D_IT C_IT
+	, sbMaterial :: V3 Float
+	-- ^ LST AST VST
+	, sbAnchorRigidBodies :: [(BodyIx, VertexIx, Bool)]
+	-- ^ True: Enable Near mode
+	, sbPinVertices :: [VertexIx]
+	} deriving Show
+
+
+-- * VMD (Vocaloid Motion Data)
+
+-- VMD strings are encoded in CP932
+-- | Vocaloid Motion Data
+data VMD = VMD
+	{ vmdModelName :: B.ByteString -- ^ CP932
+	, boneMotion :: [BoneKeyframe]
+	, morphMotion :: [MorphKeyframe]
+	, cameraMotion :: [CameraKeyframe]
+	, lightMotion :: [LightKeyframe]
+	, shadowMotion :: [ShadowKeyframe]
+	, controlMotion :: [ControlKeyframe]
+	} deriving Show
+
+data BoneKeyframe = BoneKeyframe
+	{ bkName :: B.ByteString
+	, bkFrame :: Word32
+	, bkPosition :: V3 Float -- ^ XYZ or (0,0,0)
+	, bkRotation :: V4 Float -- ^ Quaternion XYZW or (0,0,0,1)
+	, bkInterpolation :: [Int] -- XXX description
+	} deriving Show
+
+data MorphKeyframe = MorphKeyframe
+	{ moName :: B.ByteString
+	, moFrame :: Word32
+	, moFace :: Float
+	} deriving Show
+
+data CameraKeyframe = CameraKeyframe
+	{ ckFrame :: Word32
+	, ckDistance :: Float
+	, ckPosition :: V3 Float
+	, ckRotation :: V3 Float
+	, ckBezier :: [Int] -- ^ bezier int[24]
+	, ckViewingAngle :: Word32 -- ^ in degree
+	, ckPerspective :: Bool -- 0:Enable, 1:Disable
+	} deriving Show
+
+data LightKeyframe = LightKeyframe
+	{ lkFrame :: Word32
+	, lkColor :: V3 Float
+	, lkPosition :: V3 Float
+	} deriving Show
+
+data ShadowKeyframe = ShadowKeyframe
+	{ skFrame :: Word32
+	, skType :: Int
+	, skDistance :: Float -- ^ 0.1 - (dist * 0.00001)
+	} deriving Show
+
+data ControlKeyframe = ControlKeyframe
+	{ ctFrame :: Word32
+	, ctModelVisibility :: Bool
+	, ctIKCapability :: [(B.ByteString, Bool)]
+	} deriving Show
+
+
+-- * VPD
+
+-- | Vocaloid Pose Data
+-- (name of a Bone, position of the bone, quaternion of the bone)
+type VPD = (B.ByteString, V3 Float, V4 Float)
+
+
+-- * VAC
+
+-- | Vocaloid Accessory 
+-- (name of an accessory, .x filename, position, eular angle, name of the bound bone)
+type VAC = (String, FilePath, Float, V3 Float, V3 Float, String)
+
diff --git a/Graphics/Model/Obj.hs b/Graphics/Model/Obj.hs
new file mode 100644
--- /dev/null
+++ b/Graphics/Model/Obj.hs
@@ -0,0 +1,126 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE TupleSections #-}
+-- | OBJ is a widely used simple 3D model format made for TAV.
+-- The Advanced Visualizer (TAV), a 3D graphics software package,
+-- was the flagship product of Wavefront Technologies from the 1980s
+-- until the 1990s.
+-- <https://en.wikipedia.org/wiki/The_Advanced_Visualizer>
+
+module Graphics.Model.Obj (
+  loadOBJ,
+  parseOBJ,
+  --reverseHandOBJ,
+  loadMtl,
+  parseMtl,
+
+  ObjMesh(..),
+  ObjFace(..),
+  ObjMaterial(..)
+  ) where
+
+import Control.Applicative
+import Data.Attoparsec.ByteString.Char8 as A
+import qualified Data.ByteString as B
+import Data.Int
+import Linear
+
+data ObjMesh = ObjMesh
+	{ _objGroup :: String
+	, _objMaterial :: Maybe (FilePath, String)
+	, _objVertices :: [V3 Float]
+	, _objUVs :: [V2 Float]
+	, _objNormals :: [V3 Float]
+	, _objFaces :: [[ObjFace]]
+	} deriving Show
+
+data ObjFace = ObjFace
+	{ _objVertexIx :: Int32
+	, _objTextureIx :: Int32
+	, _objNormalIx :: Int32
+	} deriving Show
+
+data ObjMaterial = ObjMaterial
+	{ _objName :: String
+	, _objAmbient :: V3 Float
+	, _objDiffuse :: V3 Float
+	, _objSpecular :: V3 Float
+	, _objShineness :: Float
+	} deriving Show
+
+loadOBJ :: FilePath -> IO [ObjMesh]
+loadOBJ path = parseOBJ <$> B.readFile path
+
+parseOBJ :: B.ByteString -> [ObjMesh]
+parseOBJ bs =
+	case feed (parse parseMeshes bs) "" of
+		Done _ result -> result
+		result -> error $ "parseOBJ: Invalid format:\n" ++ show result
+
+comments = do
+	many (skipSpace >> char '#' >> A.takeWhile (/= '\n'))
+	skipSpace
+
+parseMeshes :: Parser [ObjMesh]
+parseMeshes = do
+	path <- comments *> option "" parseMtllib <* comments
+	many1 (parseMesh path <* comments)
+
+parseMesh :: String -> Parser ObjMesh
+parseMesh path = do
+	group <- option "default" parseG <* comments
+	mtl1 <- optional parseUsemtl <* comments
+	vertices <- many1 (parseV <* comments)
+	uvs <- many (parseVt <* comments)
+	normals <- many (parseVn <* comments)
+	mtl2 <- optional parseUsemtl <* comments
+	faces <- many (parseF <* comments)
+	let mtl = fmap (path,) (maybe mtl2 Just mtl1)
+	return $ ObjMesh group mtl vertices uvs normals faces
+
+getString, parseMtllib, parseG, parseUsemtl :: Parser String
+getString = many1 (satisfy (/= '\n'))
+parseMtllib = string "mtllib " >> getString
+parseG = char 'g' >> sp >> getString
+parseUsemtl = string "usemtl " >> getString
+
+parseV = char 'v' >> float3
+parseVt = char 'v' >> char 't' >> float2
+parseVn = char 'v' >> char 'n' >> float3
+parseF = char 'f' >> many indexes
+
+float = realToFrac <$> signed double
+sp = char ' '
+float3 = V3
+	<$> (sp *> float)
+	<*> (sp *> float)
+	<*> (sp *> float)
+float2 = V2
+	<$> (sp *> float)
+	<*> (sp *> float)
+
+index = option 0 decimal
+indexes = ObjFace
+	<$> (sp       *> index)
+	<*> (char '/' *> index)
+	<*> (char '/' *> index)
+
+loadMtl :: FilePath -> IO ObjMaterial
+loadMtl path = parseMtl <$> B.readFile path
+
+parseMtl :: B.ByteString -> ObjMaterial
+parseMtl bs =
+	case feed (parse parseMtl' bs) "" of
+		Done _ result -> result
+		result -> error $ "parseMtl: Invalid format:\n" ++ show result
+
+parseMtl' :: Parser ObjMaterial
+parseMtl' = do
+	comments
+	mtl <- string "newmtl " >> getString <* comments
+	amb <- string "Ka" >> float3 <* comments
+	diff <- string "Kd" >> float3 <* comments
+	spec <- string "Ks" >> float3 <* comments
+	shine <- string "Ns " >> float
+	-- XXX map_Kd
+	return $ ObjMaterial mtl amb diff spec shine
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,165 @@
+                  GNU LESSER GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+
+  This version of the GNU Lesser General Public License incorporates
+the terms and conditions of version 3 of the GNU General Public
+License, supplemented by the additional permissions listed below.
+
+  0. Additional Definitions.
+
+  As used herein, "this License" refers to version 3 of the GNU Lesser
+General Public License, and the "GNU GPL" refers to version 3 of the GNU
+General Public License.
+
+  "The Library" refers to a covered work governed by this License,
+other than an Application or a Combined Work as defined below.
+
+  An "Application" is any work that makes use of an interface provided
+by the Library, but which is not otherwise based on the Library.
+Defining a subclass of a class defined by the Library is deemed a mode
+of using an interface provided by the Library.
+
+  A "Combined Work" is a work produced by combining or linking an
+Application with the Library.  The particular version of the Library
+with which the Combined Work was made is also called the "Linked
+Version".
+
+  The "Minimal Corresponding Source" for a Combined Work means the
+Corresponding Source for the Combined Work, excluding any source code
+for portions of the Combined Work that, considered in isolation, are
+based on the Application, and not on the Linked Version.
+
+  The "Corresponding Application Code" for a Combined Work means the
+object code and/or source code for the Application, including any data
+and utility programs needed for reproducing the Combined Work from the
+Application, but excluding the System Libraries of the Combined Work.
+
+  1. Exception to Section 3 of the GNU GPL.
+
+  You may convey a covered work under sections 3 and 4 of this License
+without being bound by section 3 of the GNU GPL.
+
+  2. Conveying Modified Versions.
+
+  If you modify a copy of the Library, and, in your modifications, a
+facility refers to a function or data to be supplied by an Application
+that uses the facility (other than as an argument passed when the
+facility is invoked), then you may convey a copy of the modified
+version:
+
+   a) under this License, provided that you make a good faith effort to
+   ensure that, in the event an Application does not supply the
+   function or data, the facility still operates, and performs
+   whatever part of its purpose remains meaningful, or
+
+   b) under the GNU GPL, with none of the additional permissions of
+   this License applicable to that copy.
+
+  3. Object Code Incorporating Material from Library Header Files.
+
+  The object code form of an Application may incorporate material from
+a header file that is part of the Library.  You may convey such object
+code under terms of your choice, provided that, if the incorporated
+material is not limited to numerical parameters, data structure
+layouts and accessors, or small macros, inline functions and templates
+(ten or fewer lines in length), you do both of the following:
+
+   a) Give prominent notice with each copy of the object code that the
+   Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the object code with a copy of the GNU GPL and this license
+   document.
+
+  4. Combined Works.
+
+  You may convey a Combined Work under terms of your choice that,
+taken together, effectively do not restrict modification of the
+portions of the Library contained in the Combined Work and reverse
+engineering for debugging such modifications, if you also do each of
+the following:
+
+   a) Give prominent notice with each copy of the Combined Work that
+   the Library is used in it and that the Library and its use are
+   covered by this License.
+
+   b) Accompany the Combined Work with a copy of the GNU GPL and this license
+   document.
+
+   c) For a Combined Work that displays copyright notices during
+   execution, include the copyright notice for the Library among
+   these notices, as well as a reference directing the user to the
+   copies of the GNU GPL and this license document.
+
+   d) Do one of the following:
+
+       0) Convey the Minimal Corresponding Source under the terms of this
+       License, and the Corresponding Application Code in a form
+       suitable for, and under terms that permit, the user to
+       recombine or relink the Application with a modified version of
+       the Linked Version to produce a modified Combined Work, in the
+       manner specified by section 6 of the GNU GPL for conveying
+       Corresponding Source.
+
+       1) Use a suitable shared library mechanism for linking with the
+       Library.  A suitable mechanism is one that (a) uses at run time
+       a copy of the Library already present on the user's computer
+       system, and (b) will operate properly with a modified version
+       of the Library that is interface-compatible with the Linked
+       Version.
+
+   e) Provide Installation Information, but only if you would otherwise
+   be required to provide such information under section 6 of the
+   GNU GPL, and only to the extent that such information is
+   necessary to install and execute a modified version of the
+   Combined Work produced by recombining or relinking the
+   Application with a modified version of the Linked Version. (If
+   you use option 4d0, the Installation Information must accompany
+   the Minimal Corresponding Source and Corresponding Application
+   Code. If you use option 4d1, you must provide the Installation
+   Information in the manner specified by section 6 of the GNU GPL
+   for conveying Corresponding Source.)
+
+  5. Combined Libraries.
+
+  You may place library facilities that are a work based on the
+Library side by side in a single library together with other library
+facilities that are not Applications and are not covered by this
+License, and convey such a combined library under terms of your
+choice, if you do both of the following:
+
+   a) Accompany the combined library with a copy of the same work based
+   on the Library, uncombined with any other library facilities,
+   conveyed under the terms of this License.
+
+   b) Give prominent notice with the combined library that part of it
+   is a work based on the Library, and explaining where to find the
+   accompanying uncombined form of the same work.
+
+  6. Revised Versions of the GNU Lesser General Public License.
+
+  The Free Software Foundation may publish revised and/or new versions
+of the GNU Lesser General Public License from time to time. Such new
+versions will be similar in spirit to the present version, but may
+differ in detail to address new problems or concerns.
+
+  Each version is given a distinguishing version number. If the
+Library as you received it specifies that a certain numbered version
+of the GNU Lesser General Public License "or any later version"
+applies to it, you have the option of following the terms and
+conditions either of that published version or of any later version
+published by the Free Software Foundation. If the Library as you
+received it does not specify a version number of the GNU Lesser
+General Public License, you may choose any version of the GNU Lesser
+General Public License ever published by the Free Software Foundation.
+
+  If the Library as you received it specifies that a proxy can decide
+whether future versions of the GNU Lesser General Public License shall
+apply, that proxy's public statement of acceptance of any version is
+permanent authorization for you to choose that version for the
+Library.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
