/// <summary> /// Loads a Fantasy Life ZMDL model. /// Note that ZMDL must start at offset 0x0 (don't try using it for ZMDLs inside containers). /// </summary> /// <param name="data">Stream of the ZMDL file.</param> /// <returns></returns> public static RenderBase.OModelGroup load(Stream data) { BinaryReader input = new BinaryReader(data); RenderBase.OModelGroup models = new RenderBase.OModelGroup(); RenderBase.OModel model = new RenderBase.OModel(); model.name = "model"; string zmdlMagic = IOUtils.readString(input, 0, 4); data.Seek(0x20, SeekOrigin.Begin); uint materialsOffset = input.ReadUInt32(); uint skeletonOffset = input.ReadUInt32(); uint modelOffset = input.ReadUInt32(); ushort materialsCount = input.ReadUInt16(); ushort bonesCount = input.ReadUInt16(); ushort modelObjectsCount = input.ReadUInt16(); ushort unknowCount = input.ReadUInt16(); //Materials List <byte> materialObjectBinding = new List <byte>(); for (int materialIndex = 0; materialIndex < materialsCount; materialIndex++) { RenderBase.OMaterial material = new RenderBase.OMaterial(); material.name = IOUtils.readString(input, (uint)(materialsOffset + materialIndex * 0xb4)); data.Seek(materialsOffset + (materialIndex * 0xb4) + 0x94, SeekOrigin.Begin); uint textureReferenceOffset = input.ReadUInt32(); uint objectReferenceIndexOffset = input.ReadUInt32(); data.Seek(objectReferenceIndexOffset, SeekOrigin.Begin); materialObjectBinding.Add(input.ReadByte()); material.name0 = IOUtils.readString(input, textureReferenceOffset); data.Seek(textureReferenceOffset + 0x40, SeekOrigin.Begin); while ((data.Position & 3) != 0) { input.ReadByte(); //Align Word } data.Seek(0x30, SeekOrigin.Current); //Unknown matrix (possibly UV transform) input.ReadUInt32(); input.ReadByte(); input.ReadByte(); byte wrap = input.ReadByte(); input.ReadByte(); model.material.Add(material); } //Skeleton for (int boneIndex = 0; boneIndex < bonesCount; boneIndex++) { RenderBase.OBone bone = new RenderBase.OBone(); bone.name = IOUtils.readString(input, (uint)(skeletonOffset + boneIndex * 0xcc)); data.Seek(skeletonOffset + (boneIndex * 0xcc) + 0x40, SeekOrigin.Begin); data.Seek(0x64, SeekOrigin.Current); //Unknow matrices, probably transform and other stuff bone.translation = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); bone.rotation = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); bone.scale = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); bone.absoluteScale = new RenderBase.OVector3(bone.scale); bone.parentId = input.ReadInt16(); input.ReadUInt16(); model.skeleton.Add(bone); } //Meshes for (int objIndex = 0; objIndex < modelObjectsCount; objIndex++) { RenderBase.OMesh obj = new RenderBase.OMesh(); obj.name = string.Format("mesh_{0}", objIndex); data.Seek(modelOffset + objIndex * 0xc4, SeekOrigin.Begin); attributeEntry[] attributes = new attributeEntry[9]; for (int attribute = 0; attribute < 9; attribute++) { attributes[attribute].floats = new RenderBase.OVector4( input.ReadSingle(), input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); attributes[attribute].offset = input.ReadByte() * 4; attributes[attribute].attributeLength = input.ReadByte(); attributes[attribute].stride = input.ReadUInt16() * 4; if (attributes[attribute].attributeLength > 0) { switch (attribute) { case aNormal: obj.hasNormal = true; break; case aColor: obj.hasColor = true; break; case aTex0: obj.texUVCount = 1; break; case aNode: obj.hasNode = true; break; case aWeight: obj.hasWeight = true; break; } } } int vertexStride = attributes[8].stride; uint facesHeaderOffset = input.ReadUInt32(); uint facesHeaderEntries = input.ReadUInt32(); uint vertexBufferOffset = input.ReadUInt32(); uint vertexBufferLength = input.ReadUInt32() * 4; for (int faceIndex = 0; faceIndex < facesHeaderEntries; faceIndex++) { data.Seek(facesHeaderOffset + faceIndex * 0x14, SeekOrigin.Begin); uint boneNodesOffset = input.ReadUInt32(); uint boneNodesEntries = input.ReadUInt32(); uint indexBufferOffset = input.ReadUInt32(); uint indexBufferPrimitiveCount = input.ReadUInt32(); input.ReadUInt32(); data.Seek(boneNodesOffset, SeekOrigin.Begin); List <byte> nodeList = new List <byte>(); for (int n = 0; n < boneNodesEntries; n++) { nodeList.Add(input.ReadByte()); } data.Seek(indexBufferOffset, SeekOrigin.Begin); for (int face = 0; face < indexBufferPrimitiveCount; face++) { ushort index = input.ReadUInt16(); RenderBase.OVertex vertex = new RenderBase.OVertex(); vertex.diffuseColor = 0xffffffff; long position = data.Position; long vertexOffset = vertexBufferOffset + index * vertexStride; data.Seek(vertexOffset + attributes[aPosition].offset, SeekOrigin.Begin); vertex.position = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); if (attributes[aNormal].attributeLength > 0) { data.Seek(vertexOffset + attributes[aNormal].offset, SeekOrigin.Begin); vertex.normal = new RenderBase.OVector3(input.ReadSingle(), input.ReadSingle(), input.ReadSingle()); } if (attributes[aColor].attributeLength > 0) { data.Seek(vertexOffset + attributes[aColor].offset, SeekOrigin.Begin); uint r = MeshUtils.saturate(input.ReadSingle() * 0xff); uint g = MeshUtils.saturate(input.ReadSingle() * 0xff); uint b = MeshUtils.saturate(input.ReadSingle() * 0xff); uint a = MeshUtils.saturate(input.ReadSingle() * 0xff); vertex.diffuseColor = b | (g << 8) | (r << 16) | (a << 24); } if (attributes[aTex0].attributeLength > 0) { data.Seek(vertexOffset + attributes[aTex0].offset, SeekOrigin.Begin); vertex.texture0 = new RenderBase.OVector2(input.ReadSingle(), input.ReadSingle()); } for (int boneIndex = 0; boneIndex < attributes[aNode].attributeLength; boneIndex++) { data.Seek(vertexOffset + attributes[aNode].offset + (boneIndex * 4), SeekOrigin.Begin); vertex.node.Add(nodeList[(int)input.ReadSingle()]); } for (int boneWeight = 0; boneWeight < attributes[aWeight].attributeLength; boneWeight++) { data.Seek(vertexOffset + attributes[aWeight].offset + (boneWeight * 4), SeekOrigin.Begin); vertex.weight.Add(input.ReadSingle()); } MeshUtils.calculateBounds(model, vertex); obj.vertices.Add(vertex); data.Seek(position, SeekOrigin.Begin); } } int materialId = materialObjectBinding.IndexOf((byte)objIndex); if (materialId > -1) { obj.materialId = (ushort)materialId; } model.mesh.Add(obj); } data.Close(); models.model.Add(model); return(models); }
public static RenderBase.OModelGroup load(string fileName) { FileStream data = new FileStream(fileName, FileMode.Open); BinaryReader input = new BinaryReader(data); RenderBase.OModelGroup models; RenderBase.OModel model; string extension = Path.GetExtension(fileName).ToLower(); string bchFile = fileName.Replace(extension, ".bch"); bool isBCHLoaded = false; if (File.Exists(bchFile)) { models = BCH.load(bchFile); model = models.model[0]; models.model.Clear(); isBCHLoaded = true; } else { models = new RenderBase.OModelGroup(); model = new RenderBase.OModel(); model.name = "model"; model.material.Add(new RenderBase.OMaterial()); } ushort format = input.ReadUInt16(); bool isDataWithinHeader = format == 4; input.ReadUInt16(); //-1? uint contentFlags = input.ReadUInt32(); bool hasNameTable = (contentFlags & 2) > 0; uint mode = input.ReadUInt32(); uint meshCount = input.ReadUInt32(); List <vtxEntry> vtxDescriptors = new List <vtxEntry>(); List <idxEntry> idxDescriptors = new List <idxEntry>(); for (int i = 0; i < meshCount; i++) { if (mode == 1 && i == 0) { vtxDescriptors.Add(getVtxDescriptor(input)); } uint facesCount = input.ReadUInt32(); for (int j = 0; j < facesCount; j++) { idxEntry face = new idxEntry(); face.meshIndex = i; uint nodesCount = input.ReadUInt32(); for (int k = 0; k < nodesCount; k++) { face.nodeList.Add(input.ReadUInt32()); } face.primitiveCount = input.ReadUInt32(); if (hasNameTable) { face.nameId = input.ReadUInt32(); } if (isDataWithinHeader) { face.buffer = new ushort[face.primitiveCount]; for (int k = 0; k < face.primitiveCount; k++) { face.buffer[k] = input.ReadUInt16(); } alignWord(input); } idxDescriptors.Add(face); } if (mode == 0) { if (isDataWithinHeader) { vtxEntry desc = getVtxDescriptor(input); desc.buffer = new byte[desc.length]; input.Read(desc.buffer, 0, desc.buffer.Length); vtxDescriptors.Add(desc); alignWord(input); } else { vtxDescriptors.Add(getVtxDescriptor(input)); } } } List <string> objNameTable = new List <string>(); if (hasNameTable) { for (int i = 0; i < meshCount; i++) { byte index = input.ReadByte(); objNameTable.Add(IOUtils.readString(input, (uint)data.Position, true)); } } if (!isDataWithinHeader) { align(input); } byte[] vtxBuffer = null; vtxEntry currVertex = null; int faceIndex = 0; for (int i = 0; i < meshCount; i++) { if (mode == 0 || i == 0) { currVertex = vtxDescriptors[i]; if (!isDataWithinHeader) { vtxBuffer = new byte[vtxDescriptors[i].length]; input.Read(vtxBuffer, 0, vtxBuffer.Length); align(input); } else { vtxBuffer = currVertex.buffer; } } RenderBase.OMesh obj; if (isBCHLoaded) { obj = model.mesh[0]; model.mesh.RemoveAt(0); } else { obj = new RenderBase.OMesh(); obj.name = "mesh_" + i.ToString(); } for (int j = 0; j < currVertex.attributes.Count; j++) { switch (currVertex.attributes[j].type) { case vtxAttributeType.normal: obj.hasNormal = true; break; case vtxAttributeType.color: obj.hasColor = true; break; case vtxAttributeType.textureCoordinate0: obj.texUVCount = 1; break; case vtxAttributeType.textureCoordinate1: obj.texUVCount = 2; break; case vtxAttributeType.boneIndex: obj.hasNode = true; break; case vtxAttributeType.boneWeight: obj.hasWeight = true; break; } } for (;;) { int indexBufferPos = 0; for (int j = 0; j < idxDescriptors[faceIndex].primitiveCount; j++) { ushort index; if (isDataWithinHeader) { index = idxDescriptors[faceIndex].buffer[indexBufferPos++]; } else { index = input.ReadUInt16(); } RenderBase.OVertex vertex = new RenderBase.OVertex(); vertex.diffuseColor = 0xffffffff; for (int k = 0; k < currVertex.attributes.Count; k++) { vtxAttribute att = currVertex.attributes[k]; int pos = (int)(index * currVertex.stride + att.offset); float scale = att.scale; switch (currVertex.attributes[k].type) { case vtxAttributeType.position: vertex.position = getVector3(vtxBuffer, pos, att.format, scale); break; case vtxAttributeType.normal: vertex.normal = getVector3(vtxBuffer, pos, att.format, scale); break; case vtxAttributeType.color: RenderBase.OVector4 c = getVector4(vtxBuffer, pos, att.format, scale); uint r = MeshUtils.saturate(c.x * 0xff); uint g = MeshUtils.saturate(c.y * 0xff); uint b = MeshUtils.saturate(c.z * 0xff); uint a = MeshUtils.saturate(c.w * 0xff); vertex.diffuseColor = b | (g << 8) | (r << 16) | (a << 24); break; case vtxAttributeType.textureCoordinate0: vertex.texture0 = getVector2(vtxBuffer, pos, att.format, scale); break; case vtxAttributeType.textureCoordinate1: vertex.texture1 = getVector2(vtxBuffer, pos, att.format, scale); break; case vtxAttributeType.boneIndex: byte n0 = vtxBuffer[pos]; byte n1 = vtxBuffer[pos + 1]; vertex.node.Add((int)idxDescriptors[faceIndex].nodeList[n0]); vertex.node.Add((int)idxDescriptors[faceIndex].nodeList[n1]); break; case vtxAttributeType.boneWeight: RenderBase.OVector2 w = getVector2(vtxBuffer, pos, att.format, scale); vertex.weight.Add(w.x); vertex.weight.Add(w.y); break; } } MeshUtils.calculateBounds(model, vertex); obj.vertices.Add(vertex); } faceIndex++; if (!isDataWithinHeader) { align(input); } if (faceIndex >= idxDescriptors.Count) { break; } if (idxDescriptors[faceIndex].meshIndex == i) { continue; } break; } model.mesh.Add(obj); } models.model.Add(model); data.Close(); return(models); }