Jump to content

Ultra model file format


Josh
 Share

Recommended Posts

A small change was needed for LOD distances. The current version is now 301. A Vec3 was added in the node properties. The old per-LOD value will be ignored.

If you don't know what the LOD distances should be, just fill it with NAN values.

See G3DModelLoader::LoadNode() below.

The original version 300 models will continue to load with no changes.

#include "UltraEngine.h"

using namespace UltraEngine;

namespace UltraEngine::Core
{
	G3DModelLoader::G3DModelLoader()
	{
		extensions = { L"mdl" };
	}

	String G3DModelLoader::ReadText(shared_ptr<Stream> stream)
	{
		int len = stream->ReadInt();
		auto pos = stream->GetPosition();
		String s;
		if (len)
		{
			s = stream->ReadString(len);
			stream->Seek(pos + len);
		}
		return s;
	}
	
	bool G3DModelLoader::Reload(shared_ptr<Stream> stream, shared_ptr<Object> o, const LoadFlags flags)
	{
		auto modelbase = o->As<ModelBase>();
		if (modelbase == NULL) return false;
		modelbase->model = CreateModel(NULL);
		auto model = modelbase->model->As<Model>();

		auto start = stream->GetPosition();
		if (stream->ReadString(12) != "Ultra Model") return false;
		Assert(stream->GetPosition() == start + 12);
		this->version = stream->ReadInt();
		if (version != 300 and version != 301)
		{
			Print("Error: MDL version " + String(version) + " not supported");
			return false;
		}

		return LoadNode(stream, model, flags);
	}

	bool G3DModelLoader::LoadNode(shared_ptr<Stream> stream, shared_ptr<Model> model, const LoadFlags flags)
	{
		Vec3 pos, scale, lodrange;
		Vec4 color;
		Quat rot;
		String s;
		if (stream->ReadString(4) != "NODE")
		{
			Print("Error: Expected NODE tag");
			return false;
		}

		model->name = ReadText(stream);
		model->properties = ParseJson(ReadText(stream));
		ParseJson(ReadText(stream));

		pos.x = stream->ReadFloat(); pos.y = stream->ReadFloat(); pos.z = stream->ReadFloat();
		rot.x = stream->ReadFloat(); rot.y = stream->ReadFloat(); rot.z = stream->ReadFloat(); rot.w = stream->ReadFloat();
		scale.x = stream->ReadFloat(); scale.y = stream->ReadFloat(); scale.z = stream->ReadFloat();
		color.x = stream->ReadFloat(); color.y = stream->ReadFloat(); color.z = stream->ReadFloat(); color.a = stream->ReadFloat();
		if (version > 300)
		{
			lodrange.x = stream->ReadFloat(); lodrange.y = stream->ReadFloat(); lodrange.z = stream->ReadFloat();
		}
		model->SetPosition(pos);
		model->SetRotation(rot);
		model->SetScale(scale);
		model->SetColor(color);
		if (version > 300)
		{
			auto range = model->GetLodDistance();
			if (not isnan(lodrange.x)) range.x = lodrange.x;
			if (not isnan(lodrange.y)) range.y = lodrange.y;
			if (not isnan(lodrange.z)) range.z = lodrange.z;
			model->SetLodDistance(range.x, range.y, range.z);
		}

		int countlods = stream->ReadInt();
		for (int level = 0; level < countlods; ++level)
		{
			if (not LoadLod(stream, model, level, flags)) return false;
		}

		// Skeleton
		if (stream->ReadString(4) != "SKEL")
		{
			Print("Error: Expected SKEL tag");
			return false;
		}
		int bones = stream->ReadInt();
		if (bones)
		{
			if (model->GetParent())
			{
				Print("Error: Skeleton can only appear in the model root node");
				return false;
			}
			if (bones < 0)
			{
				Print("Error: Skeleton bones must be more than zero");
				return false;
			}
			auto skeleton = CreateSkeleton(nullptr);
			skeleton->root = std::make_shared<Bone>(nullptr, skeleton);
			skeleton->bones.resize(bones);
			if (not LoadBone(stream, skeleton, skeleton->root, 0, flags)) return false;
			skeleton->root->UpdateMatrix();
			skeleton->bones[0] = skeleton->root;
			skeleton->UpdateSkinning();
			for (int n = 0; n < skeleton->bones.size(); ++n)
			{
				if (skeleton->bones[n] == NULL) continue;
				auto bone = skeleton->bones[n];
				bone->inversebindmatrix = bone->matrix.Inverse();
				bone->animbone->inversebindmatrix = bone->inversebindmatrix;
				bone->Finalize();
			}
			model->SetSkeleton(skeleton);
		}

		//Attachment
		if (stream->ReadString(4) != "ATCH")
		{
			Print("Error: Expected ATCH tag");
			return false;
		}
		int attachmentboneid = stream->ReadInt();
		if (attachmentboneid != -1)
		{
			stream->Seek(stream->GetPosition() + 64);
		}

		// Animations
		if (stream->ReadString(4) != "ASET")
		{
			Print("Error: Expected ASET tag");
			return false;
		}
		int animations = stream->ReadInt();
		if (animations)
		{
			if (model->GetParent())
			{
				Print("Error: Animations can only appear in the model root node");
				return false;
			}
			for (int anim = 0; anim < animations; ++anim)
			{
				if (stream->ReadString(4) != "ANIM")
				{
					Print("Error: Expected ANIM tag");
					return false;
				}

				auto seq = std::make_shared<Sequence>();
				model->skeleton->root->animations.push_back(seq);
				model->skeleton->root->animbone->animations.push_back(seq);

				WString animname = ReadText(stream);
				float speed = stream->ReadFloat();
				int keyframes = stream->ReadInt();
				float duration = float(keyframes) / 60.0f * speed;

				int bones = stream->ReadInt();
				for (int b = 0; b < bones; ++b)
				{
					if (stream->ReadString(4) != "BONE")
					{
						Print("Error: Expected BONE tag");
						return false;
					}
					int keyflags = stream->ReadInt();
					auto bone = model->skeleton->bones[b];
					bone->animations.resize(animations);
					bone->animations[anim] = std::make_shared<Sequence>();
					bone->animations[anim]->name = animname;
					bone->animations[anim]->speed = speed;
					bone->animations[anim]->keyframes.reserve(keyframes);
					bone->animations[anim]->duration = duration;
					KeyFrame key;
					for (int k = 0; k < keyframes; ++k)
					{
						if ((1 & keyflags) != 0)
						{
							key.position.x = stream->ReadFloat();
							key.position.y = stream->ReadFloat();
							key.position.z = stream->ReadFloat();
						}
						if ((2 & keyflags) != 0)
						{
							key.rotation.x = stream->ReadFloat();
							key.rotation.y = stream->ReadFloat();
							key.rotation.z = stream->ReadFloat();
							key.rotation.w = stream->ReadFloat();
						}
						if ((4 & keyflags) != 0)
						{
							key.scale = stream->ReadFloat();
							stream->ReadFloat();
							stream->ReadFloat();
						}
						bone->animations[anim]->keyframes.push_back(key);
					}
					bone->animbone->animations = bone->animations;
				}
			}
		}

		// Collider
		if (stream->ReadString(4) != "PHYS")
		{
			auto pos = stream->GetPosition() - 4;
			Print("Error: Expected PHYS tag at position " + String(pos));
			return false;
		}
		int colliderdatasize = stream->ReadInt();
		auto colliderstartposition = stream->GetPosition();
		if (colliderdatasize)
		{
			Vec3 position, scale, euler;
			Quat rotation;
			std::vector<shared_ptr<Collider> > parts;
			int partcount = stream->ReadInt();
			for (int n = 0; n < partcount; ++n)
			{
				if (stream->ReadString(4) != "PART")
				{
					auto pos = stream->GetPosition() - 4;
					Print("Error: Expected PART tag at position " + String(pos));
					return false;
				}
				shared_ptr<Collider> part;
				auto tag = stream->ReadString(4);
				if (tag == "HULL")
				{
					float tol = stream->ReadFloat();// tolerance
					std::vector<Vec3> points;
					Vec3 p;
					int count = stream->ReadInt();
					for (int n = 0; n < count; ++n)
					{
						p.x = stream->ReadFloat();
						p.y = stream->ReadFloat();
						p.z = stream->ReadFloat();
						points.push_back(p);
					}
					part = CreateConvexHullCollider(points, 0.0f);
					if (part) part->tolerance = tol;
				}
				else if (tag == "MESH")
				{
					int opt = stream->ReadInt();// optimize flag
					int count = stream->ReadInt();
					int vcount;
					std::vector<Vec3> face;
					Vec3 p;
					std::vector<std::vector<Vec3> > meshfaces;
					for (int n = 0; n < count; ++n)
					{
						vcount = stream->ReadInt();
						face.clear();
						for (int v = 0; v < vcount; ++v)
						{
							p.x = stream->ReadFloat();
							p.y = stream->ReadFloat();
							p.z = stream->ReadFloat();
							face.push_back(p);
						}
						meshfaces.push_back(face);
					}
					part = CreateMeshCollider(meshfaces, false);
					if (part) part->optimizemesh = opt;
				}
				else
				{
					position.x = stream->ReadFloat(); position.y = stream->ReadFloat(); position.z = stream->ReadFloat();
					rotation.x = stream->ReadFloat(); rotation.y = stream->ReadFloat(); rotation.z = stream->ReadFloat(); rotation.w = stream->ReadFloat();
					scale.x = stream->ReadFloat(); scale.y = stream->ReadFloat(); scale.z = stream->ReadFloat();
					if (tag == "BOX_")
					{
						part = CreateBoxCollider(scale, position, rotation.Euler());						
					}
					else if (tag == "CYLI")
					{
						part = CreateCylinderCollider(scale.x, scale.y, position, rotation.Euler());
					}
					else if (tag == "CONE")
					{
						part = CreateConeCollider(scale.x, scale.y, position, rotation.Euler());
					}
					else if (tag == "CCYL")
					{
						part = CreateChamferCylinderCollider(scale.x, scale.y, position, rotation.Euler());
					}
					else if (tag == "CAPS")
					{
						part = CreateCapsuleCollider(scale.x, scale.y, position, rotation.Euler());
					}
					else if (tag == "SPHE")
					{
						part = CreateSphereCollider(scale.x, position);
					}
					else
					{
						Print("Error: Unknown collider type \"" + tag + "\"");
						return false;
					}
				}
				if (part) parts.push_back(part);
			}
			if (parts.size())
			{
				if (parts.size() == 1)
				{
					model->SetCollider(parts[0]);
				}
				else
				{
					auto c = CreateCompoundCollider(parts);
					model->SetCollider(c);
				}
			}
			stream->Seek(colliderstartposition + colliderdatasize);
		}

		// Load children
		if (stream->ReadString(4) != "KIDS")
		{
			Print("Error: Expected KIDS tag");
			return false;
		}
		int countkids = stream->ReadInt();
		for (int n = 0; n < countkids; ++n)
		{
			auto child = CreateModel(NULL);
			child->SetParent(model);
			if (not LoadNode(stream, child, flags)) return false;
		}

		model->UpdateBounds();
		return true;
	}

	bool G3DModelLoader::LoadLod(shared_ptr<Stream> stream, shared_ptr<Model> model, const int level, const LoadFlags flags)
	{
		if (stream->ReadString(4) != "LOD_")
		{
			Print("Error: Expected LOD_ tag");
			return false;
		}

		if (level >= model->lods.size()) model->AddLod();
		float loddistance = stream->ReadFloat();
		if (version < 301)
		{
			if (loddistance > 0.0f and model->lods.size() > 0 and model->lods.size() <= 4)
			{
				auto lods = model->GetLodDistance();
				int i = int(model->lods.size()) - 2;
				lods[i] = loddistance;
				model->SetLodDistance(lods.x, lods.y, lods.z);
			}
		}
		int countmeshes = stream->ReadInt();
		for (int m = 0; m < countmeshes; ++m)
		{
			if (not LoadMesh(stream, model, level, flags)) return false;
		}
		return true;
	}

	bool G3DModelLoader::LoadMesh(shared_ptr<Stream> stream, shared_ptr<Model> model, const int level, const LoadFlags flags)
	{
		if (stream->ReadString(4) != "MESH")
		{
			Print("Error: Expected MESH tag");
			return false;
		}
		
		MeshPrimitives type = MeshPrimitives(stream->ReadInt());
		if (type < 1 or type > 4)
		{
			Print("Error: Mesh type must be between one and four");
			return false;
		}

		auto mesh = model->AddMesh(type, level);

		mesh->name = ReadText(stream);

		WString mtlpath = ReadText(stream);
		if (not mtlpath.empty())
		{
			if (mtlpath.Left(2) == "./" and not stream->path.empty())
			{
				mtlpath = ExtractDir(stream->path) + "/" + mtlpath;
			}
			auto mtl = LoadMaterial(mtlpath, flags);
			if (mtl) mesh->SetMaterial(mtl);
		}

		int vertexstride = stream->ReadInt();
		if (vertexstride != 88)
		{
			Print("Vertext stride must be 84");
			return false;
		}
		int vertexcount = stream->ReadInt();

		mesh->m_vertices.resize(vertexcount);
		for (int v = 0; v < vertexcount; ++v)
		{
			mesh->m_vertices[v].position.x = stream->ReadFloat();
			mesh->m_vertices[v].position.y = stream->ReadFloat();
			mesh->m_vertices[v].position.z = stream->ReadFloat();

			mesh->m_vertices[v].normal.x = stream->ReadFloat();
			mesh->m_vertices[v].normal.y = stream->ReadFloat();
			mesh->m_vertices[v].normal.z = stream->ReadFloat();
			
			mesh->m_vertices[v].texcoords.x = stream->ReadFloat();
			mesh->m_vertices[v].texcoords.y = stream->ReadFloat();
			mesh->m_vertices[v].texcoords.z = stream->ReadFloat();
			mesh->m_vertices[v].texcoords.w = stream->ReadFloat();
			
			mesh->m_vertices[v].color.r = float(stream->ReadByte()) / 255.0f;
			mesh->m_vertices[v].color.g = float(stream->ReadByte()) / 255.0f;
			mesh->m_vertices[v].color.b = float(stream->ReadByte()) / 255.0f;
			mesh->m_vertices[v].color.a = float(stream->ReadByte()) / 255.0f;
			
			mesh->m_vertices[v].displacement = stream->ReadFloat();
			
			mesh->m_vertices[v].tangent.x = stream->ReadFloat();
			mesh->m_vertices[v].tangent.y = stream->ReadFloat();
			mesh->m_vertices[v].tangent.z = stream->ReadFloat();
			
			mesh->m_vertices[v].bitangent.x = stream->ReadFloat();
			mesh->m_vertices[v].bitangent.y = stream->ReadFloat();
			mesh->m_vertices[v].bitangent.z = stream->ReadFloat();
			
			mesh->m_vertices[v].boneindices[0] = stream->ReadShort();
			mesh->m_vertices[v].boneindices[1] = stream->ReadShort();
			mesh->m_vertices[v].boneindices[2] = stream->ReadShort();
			mesh->m_vertices[v].boneindices[3] = stream->ReadShort();

			mesh->m_vertices[v].boneweights.x = float(stream->ReadByte()) / 255.0f;
			mesh->m_vertices[v].boneweights.y = float(stream->ReadByte()) / 255.0f;
			mesh->m_vertices[v].boneweights.z = float(stream->ReadByte()) / 255.0f;
			mesh->m_vertices[v].boneweights.w = float(stream->ReadByte()) / 255.0f;

			//SubD normals
			stream->ReadFloat(); stream->ReadFloat(); stream->ReadFloat();
		}

		Vec4 zero;
		for (const auto& vertex : mesh->vertices)
		{
			if (vertex.boneweights != zero)
			{
				mesh->SetSkinned(true);
				break;
			}
		}

		int indicesize = stream->ReadInt();
		int indicecount = stream->ReadInt();

		uint32_t index;		
		switch (indicesize)
		{
		case 2:
			mesh->m_indices.reserve(indicecount);
			for (int i = 0; i < indicecount; ++i) mesh->AddIndice(stream->ReadShort());
			break;
		case 4:
			mesh->m_indices.resize(indicecount);
			stream->Read(mesh->m_indices.data(), indicecount * sizeof(mesh->indices[0]));
			break;
		default:
			return false;
		}

		//Primitives flags
		if (stream->ReadString(4) != "PRIM")
		{
			Print("Error: Expected PRIM tag");
			return false;
		}
		int primcount = stream->ReadInt();
		if (primcount)
		{
			if (primcount != indicecount / type)
			{
				Print("Error: Primitives count must be equal to the number of primitives in the mesh, or zero");
				return false;
			}
			stream->Seek(stream->GetPosition() + primcount);
		}

		//Vertex morphs
		if (stream->ReadString(4) != "MSET")
		{
			Print("Error: Expected MORP tag");
			return false;
		}
		int morphcount = stream->ReadInt();
		for (int m = 0; m < morphcount; ++m)
		{
			if (stream->ReadString(4) != "MORP")
			{
				Print("Error: Expected MORP tag");
				return false;
			}

			if (stream->ReadInt() != 48) return false;

			for (int v = 0; v < vertexcount; ++v)
			{
				// Position
				stream->ReadFloat(); stream->ReadFloat(); stream->ReadFloat();

				// Normal
				stream->ReadFloat(); stream->ReadFloat(); stream->ReadFloat();

				// Tangent
				stream->ReadFloat(); stream->ReadFloat(); stream->ReadFloat();

				// Bitangent
				stream->ReadFloat(); stream->ReadFloat(); stream->ReadFloat();
			}
		}

		// Pick structure cache
		if (stream->ReadString(4) != "PICK")
		{
			Print("Error: Expected PICK tag");
			return false;
		}
		int pickcachesize = stream->ReadInt();
		if (pickcachesize) stream->Seek(stream->GetPosition() + pickcachesize);

		mesh->UpdateBounds();
		return true;
	}

	bool G3DModelLoader::LoadBone(shared_ptr<Stream> stream, shared_ptr<Skeleton> skeleton, shared_ptr<Bone> bone, const int animcount, const LoadFlags flags)
	{
		if (stream->ReadString(4) != "BONE")
		{
			Print("Error: Expected BONE tag");
			return false;
		}

		bone->m_id = stream->ReadInt();
		bone->animbone->id = bone->id;

		//Print(bone->m_id);
		
		if (bone->id >= skeleton->bones.size()) skeleton->bones.resize(bone->id + 1);
		skeleton->bones[bone->id] = bone;
		if (bone->id >= skeleton->animskeleton->bones.size()) skeleton->animskeleton->bones.resize(bone->id + 1);
		skeleton->animskeleton->bones[bone->id] = bone->animbone;

		bone->name = ReadText(stream);

		bone->position.x = stream->ReadFloat(); bone->position.y = stream->ReadFloat(); bone->position.z = stream->ReadFloat();
		
		bone->quaternion.x = stream->ReadFloat(); bone->quaternion.y = stream->ReadFloat(); bone->quaternion.z = stream->ReadFloat(); bone->quaternion.w = stream->ReadFloat();

		bone->scale = stream->ReadFloat(); stream->ReadFloat(); stream->ReadFloat();// scale y and z not supported

		/*int count = stream->ReadInt();
		if (bone->GetParent() and count != animcount)
		{
			Print("Error: Bone animation count must match that of the root node");
			return false;
		}

		int i = stream->GetPosition();

		for (int anim = 0; anim < count; ++anim)
		{
			auto seq = std::make_shared<Sequence>();
			bone->animations.push_back(seq);
			bone->animbone->animations.push_back(seq);

			if (stream->ReadString(4) != "ANIM")
			{
				Print("Error: Expected ANIM tag");
				return false;
			}
			
			bone->animations[anim]->name = ReadText(stream);
			bone->animations[anim]->speed = stream->ReadFloat();
			int keyflags = stream->ReadInt();
			int keyframes = stream->ReadInt();
			if (not keyflags or not keyframes) continue;
			bone->animations[anim]->duration = float(keyframes) / 60.0f * bone->animations[anim]->speed;

			KeyFrame key;
			bone->animations[anim]->keyframes.reserve(keyframes);
			for (int k = 0; k < keyframes; ++k)
			{
				if ((1 & keyflags) != 0)
				{
					key.position.x = stream->ReadFloat();
					key.position.y = stream->ReadFloat();
					key.position.z = stream->ReadFloat();
				}
				if ((2 & keyflags) != 0)
				{
					key.rotation.x = stream->ReadFloat();
					key.rotation.y = stream->ReadFloat();
					key.rotation.z = stream->ReadFloat();
					key.rotation.w = stream->ReadFloat();
				}
				if ((4 & keyflags) != 0)
				{
					key.scale = stream->ReadFloat();
					stream->ReadFloat();
					stream->ReadFloat();
				}
				bone->animations[anim]->keyframes.push_back(key);
			}			
		}
		bone->animbone->animations = bone->animations;
		*/

		if (stream->ReadString(4) != "KIDS")
		{
			Print("Error: Expected KIDS tag");
			return false;
		}
		int childcount = stream->ReadInt();
		for (int n = 0; n < childcount; ++n)
		{
			auto child = std::make_shared<Bone>(bone, skeleton);
			bone->kids.push_back(child);
			if (not LoadBone(stream, skeleton, child, bone->animations.size(), flags))
			{
				return false;
			}
		}
		
		return true;
	}
}

 

  • Thanks 1

My job is to make tools you love, with the features you want, and performance you can't live without.

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

 Share

×
×
  • Create New...