public Mesh LoadFromStream(EndianBinaryReader reader) { MeshVertexAttributeHolder vertexData = null; SceneNode rootNode = new SceneNode(); List<Texture2D> textureList = new List<Texture2D>(); List<WEditor.Common.Nintendo.J3D.Material> materialList = null; List<SkeletonBone> joints = new List<SkeletonBone>(); DrawInfo drawInfo = null; Envelopes envelopes = null; Mesh j3dMesh = new Mesh(); // Read the Header int magic = reader.ReadInt32(); // J3D1, J3D2, etc if (magic != 1244873778) { WLog.Warning(LogCategory.ModelLoading, null, "Attempted to load model with invalid magic, ignoring!"); return null; } int j3dType = reader.ReadInt32(); // BMD3 (models) BDL4 (models), jpa1 (particles), bck1 (animations), etc. int totalFileSize = reader.ReadInt32(); int chunkCount = reader.ReadInt32(); // Skip over an unused tag (consistent in all files) and some padding. reader.ReadBytes(16); for (int i = 0; i < chunkCount; i++) { long chunkStart = reader.BaseStream.Position; string tagName = reader.ReadString(4); int chunkSize = reader.ReadInt32(); switch (tagName) { // INFO - Vertex Count, Scene Hierarchy case "INF1": rootNode = LoadINF1FromFile(rootNode, reader, chunkStart); break; // VERTEX - Stores vertex arrays for pos/normal/color0/tex0 etc. Contains VertexAttributes which describe // how this data is stored/laid out. case "VTX1": vertexData = LoadVTX1FromFile(reader, chunkStart, chunkSize); break; // ENVELOPES - Defines vertex weights for skinning. case "EVP1": envelopes = LoadEVP1FromStream(reader, chunkStart); break; // DRAW (Skeletal Animation Data) - Stores which matrices are weighted, and which are used directly. case "DRW1": drawInfo = LoadDRW1FromStream(reader, chunkStart); break; // JOINTS - Stores the skeletal joints (position, rotation, scale, etc.) case "JNT1": joints = LoadJNT1SectionFromStream(reader, chunkStart); break; // SHAPE - Face/Triangle information for model. case "SHP1": LoadSHP1SectionFromFile(vertexData, j3dMesh, reader, chunkStart); break; // MATERIAL - Stores materials (which describes how textures, etc. are drawn) case "MAT3": materialList = LoadMAT3SectionFromStream(reader, chunkStart, chunkSize); break; // TEXTURES - Stores binary texture images. case "TEX1": textureList = LoadTEX1FromFile(reader, chunkStart); break; // MODEL - Seems to be bypass commands for Materials and invokes GX registers directly. case "MDL3": break; } reader.BaseStream.Position = chunkStart + chunkSize; } // Resolve the texture indexes into actual textures now that we've loaded the TEX1 section. foreach (Material mat in materialList) { for (int i = 0; i < mat.TextureIndexes.Length; i++) { short index = mat.TextureIndexes[i]; if (index < 0) continue; mat.Textures[i] = textureList[index]; } } // loltests for (int i = 0; i < materialList.Count; i++) { materialList[i].VtxDesc = j3dMesh.SubMeshes[0].GetVertexDescription(); Shader shader = TEVShaderGenerator.GenerateShader(materialList[i]); materialList[i].Shader = shader; } // We're going to do something a little crazy - we're going to read the scene view and apply textures to meshes (for now) Material curMat = null; AssignMaterialsToMeshRecursive(rootNode, j3dMesh, ref curMat, materialList); List<SkeletonBone> skeleton = new List<SkeletonBone>(); BuildSkeletonRecursive(rootNode, skeleton, joints, 0); j3dMesh.Skeleton = skeleton; j3dMesh.BindPoses = envelopes.inverseBindPose; // Let's do some ugly post-processing here to see if we can't resolve all of the cross-references and turn it into // a normal computer-readable format that we can digest in our RenderSytem. { for (int i = 0; i < j3dMesh.SubMeshes.Count; i++) { MeshBatch batch = j3dMesh.SubMeshes[i]; batch.BoneWeights = new BoneWeight[batch.Vertices.Length]; for (int j = 0; j < batch.PositionMatrixIndexs.Count; j++) { // Okay so this is where it gets more complicated. The PMI gives us an index into the MatrixTable for the packet, which we // resolve and call "drawIndexes" - however we have to divide the number they give us by three for some reason, so that is // already done and now our drawIndexes array should be one-index-for-every-vertex-in-batch and it should be the index into // the draw section we need. ushort drw1Index = batch.drawIndexes[j]; // The drw1Index can be set as 0xFFFF - if so, this means that you need to use the dr1Index of the previous one. // until it is no longer 0xFFFF. int counter = 0; while (drw1Index == 0xFFFF) { drw1Index = batch.drawIndexes[j - counter]; counter++; } bool isWeighted = drawInfo.IsWeighted[drw1Index]; BoneWeight weight = new BoneWeight(); if (isWeighted) { // Something on this doesn't work for models that actually specify a PositionMatrixIndex. // So... some math is off somewhere and I don't know where for the moment. ushort numBonesAffecting = envelopes.numBonesAffecting[drw1Index]; weight.BoneIndexes = new ushort[numBonesAffecting]; weight.BoneWeights = new float[numBonesAffecting]; // "Much WTFs" ushort offset = 0; for (ushort e = 0; e < envelopes.indexRemap[drw1Index]; e++) { offset += envelopes.numBonesAffecting[e]; } offset *= 2; Matrix4 finalTransform = Matrix4.Identity; for (ushort k = 0; k < numBonesAffecting; k++) { ushort boneIndex = envelopes.indexRemap[offset + (k * 0x2)]; float boneWeight = envelopes.weights[(offset / 2) + k]; weight.BoneIndexes[k] = boneIndex; weight.BoneWeights[k] = boneWeight; // This was apaprently a partial thought I never finished or got working in the old one? :S } } else { // If the vertex isn't weighted, we just use the position from the bone matrix. SkeletonBone joint = skeleton[drawInfo.Indexes[drw1Index]]; Matrix4 translation = Matrix4.CreateTranslation(joint.Translation); Matrix4 rotation = Matrix4.CreateFromQuaternion(joint.Rotation); Matrix4 finalMatrix = rotation * translation; // Move the mesh by transforming the position by this much. // I think we can just assign full weight to the first bone index and call it good. weight.BoneIndexes = new[] { drawInfo.Indexes[drw1Index] }; weight.BoneWeights = new[] { 1f }; } batch.BoneWeights[j] = weight; } } } return j3dMesh; }