Aelve Codesearch

grep over package repositories
3dmodels-0.3.0
Graphics/Model/MikuMikuDance/Loader.hs
{-# 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