public static ObjFile FromFile(string file) { using (var r = new StreamReader(file, Encoding.Default)) return(ObjFile.FromReader(r)); }
//Import obj data, gather and reconstruct weighting data, reconstruct model with new geometry data, delete LOD models loaded public static AquaObject ImportObj(string fileName, AquaObject aqo) { bool doBitangent = false; List <SkinData> skinData = new List <SkinData>(); AquaObjectMethods.GenerateGlobalBonePalette(aqo); //Add a local version of the global bonepalette. We're reworking things so we need to do this. for (int i = 0; i < aqo.vtxlList.Count; i++) { aqo.vtxlList[i].bonePalette = new List <ushort>(); for (int b = 0; b < aqo.bonePalette.Count; b++) { aqo.vtxlList[i].bonePalette.Add((ushort)aqo.bonePalette[b]); } } //Assign skin data for comparison later and make it relativitize it to the GlobalBonePalette if (aqo.vtxlList[0].vertWeights.Count > 0) { foreach (var vtxl in aqo.vtxlList) { for (int i = 0; i < vtxl.vertPositions.Count; i++) { SkinData skin = new SkinData(); skin.pos = vtxl.vertPositions[i]; List <byte> indices = new List <byte>(new byte[4]); for (int id = 0; id < 4; id++) { var temp = (byte)aqo.bonePalette.IndexOf(vtxl.bonePalette[vtxl.vertWeightIndices[i][id]]); if (indices.Contains(temp)) //Repeats should only occur for index 0 { temp = 0; } indices[id] = temp; } skin.indices = indices.ToArray(); skin.weights = AquaObject.VTXL.SumWeightsTo1(vtxl.vertWeights[i]); skinData.Add(skin); } } } if (aqo.vtxlList[0].vertBinormalList.Count > 0) { doBitangent = true; } //House cleaning since we can't really redo these this way aqo.tempTris.Clear(); aqo.strips3.Clear(); aqo.strips2.Clear(); aqo.strips.Clear(); aqo.pset2List.Clear(); aqo.psetList.Clear(); aqo.mesh2List.Clear(); aqo.strips3Lengths.Clear(); aqo.objc.pset2Count = 0; aqo.objc.mesh2Count = 0; var obj = ObjFile.FromFile(fileName); var subMeshes = obj.Meshes.SelectMany(i => i.SubMeshes).ToArray(); var oldMESHList = aqo.meshList; aqo.meshList = new List <AquaObject.MESH>(); int totalStripsShorts = 0; int totalVerts = 0; int boneLimit; AquaObject tempModel; if (aqo.objc.type >= 0xC32) { tempModel = new NGSAquaObject(); boneLimit = 255; } else { tempModel = new ClassicAquaObject(); boneLimit = 16; } //Assemble face and vertex data. Vert data is stored with faces so that it can be split as needed for proper UV mapping etc. foreach (var mesh in subMeshes) { var tempMesh = new AquaObject.GenericTriangles(); tempMesh.name = mesh.Name; tempMesh.bonePalette = aqo.bonePalette; List <int> vertIds = new List <int>(); Dictionary <int, int> vertIdRemap = new Dictionary <int, int>(); int greatestId = 0; for (int f = 0; f < mesh.PositionFaces.Count; ++f) { var pf = mesh.PositionFaces[f]; var nf = mesh.NormalFaces[f]; var tf = mesh.TexCoordFaces[f]; tempMesh.triList.Add(new Vector3(pf.A, pf.B, pf.C)); //faces are 1 based if (!vertIds.Contains(pf.A)) { greatestId = pf.A > greatestId ? pf.A : greatestId; vertIds.Add(pf.A); } if (!vertIds.Contains(pf.B)) { greatestId = pf.B > greatestId ? pf.B : greatestId; vertIds.Add(pf.B); } if (!vertIds.Contains(pf.C)) { greatestId = pf.C > greatestId ? pf.C : greatestId; vertIds.Add(pf.C); } tempMesh.matIdList.Add(0); var vtxl = new AquaObject.VTXL(); vtxl.rawVertId = new List <int>() { pf.A, pf.B, pf.C }; vtxl.rawFaceId = new List <int>() { f, f, f }; //Undo scaling and rotate to Y up vtxl.vertPositions = new List <Vector3>() { new Vector3(obj.Positions[pf.A].X / 100, obj.Positions[pf.A].Z / 100, -obj.Positions[pf.A].Y / 100), new Vector3(obj.Positions[pf.B].X / 100, obj.Positions[pf.B].Z / 100, -obj.Positions[pf.B].Y / 100), new Vector3(obj.Positions[pf.C].X / 100, obj.Positions[pf.C].Z / 100, -obj.Positions[pf.C].Y / 100) }; vtxl.vertNormals = new List <Vector3>() { new Vector3(obj.Normals[nf.A].X, obj.Normals[nf.A].Z, -obj.Normals[nf.A].Y), new Vector3(obj.Normals[nf.B].X, obj.Normals[nf.B].Z, -obj.Normals[nf.B].Y), new Vector3(obj.Normals[nf.C].X, obj.Normals[nf.C].Z, -obj.Normals[nf.C].Y) }; vtxl.uv1List = new List <Vector2>() { new Vector2(obj.TexCoords[tf.A].X, -obj.TexCoords[tf.A].Y), new Vector2(obj.TexCoords[tf.B].X, -obj.TexCoords[tf.B].Y), new Vector2(obj.TexCoords[tf.C].X, -obj.TexCoords[tf.C].Y), }; if (aqo.vtxlList[0].vertWeights.Count > 0) { //Autoskin magic - Essentially, iterate through and get weight values from the closest match to the original for (int vt = 0; vt < vtxl.vertPositions.Count; vt++) { var maxDistance = float.MaxValue; vtxl.vertWeights.Add(new Vector4()); vtxl.vertWeightIndices.Add(null); SkinData tempWeight = new SkinData(); //Go through each original vertex and compare distances. Adjust references if it's closer foreach (var skin in skinData) { var distance = (vtxl.vertPositions[vt] - skin.pos).LengthSquared(); //Not a full true distance, just to be quicker if (distance < maxDistance) { maxDistance = distance; tempWeight = skin; if (distance == 0) { break; } } } vtxl.vertWeights[vt] = tempWeight.weights; vtxl.vertWeightIndices[vt] = tempWeight.indices; if (aqo.vtxlList[0].vertWeightsNGS.Count > 0) { ushort[] shortWeights = new ushort[] { (ushort)(tempWeight.weights.X * ushort.MaxValue), (ushort)(tempWeight.weights.Y * ushort.MaxValue), (ushort)(tempWeight.weights.Z * ushort.MaxValue), (ushort)(tempWeight.weights.W * ushort.MaxValue), }; vtxl.vertWeightsNGS.Add(shortWeights); } } } tempMesh.faceVerts.Add(vtxl); } //Set up remapp ids for (int i = 0; i < vertIds.Count; i++) { var id = vertIds[i]; vertIdRemap.Add(id, i); } //Remap Ids to verts for (int f = 0; f < tempMesh.faceVerts.Count; f++) { for (int v = 0; v < tempMesh.faceVerts[f].rawVertId.Count; v++) { tempMesh.faceVerts[f].rawVertId[v] = vertIdRemap[tempMesh.faceVerts[f].rawVertId[v]]; } } //Remap Ids to face ids for (int f = 0; f < tempMesh.triList.Count; f++) { var tri = tempMesh.triList[f]; if (vertIdRemap.ContainsKey((int)tri.X)) { tri.X = vertIdRemap[(int)tri.X]; } if (vertIdRemap.ContainsKey((int)tri.Y)) { tri.Y = vertIdRemap[(int)tri.Y]; } if (vertIdRemap.ContainsKey((int)tri.Z)) { tri.Z = vertIdRemap[(int)tri.Z]; } tempMesh.triList[f] = tri; } tempMesh.vertCount = vertIds.Count; totalVerts += vertIds.Count; aqo.tempTris.Add(tempMesh); } AquaObjectMethods.VTXLFromFaceVerts(aqo); if (aqo.objc.type < 0xC32) { AquaObjectMethods.BatchSplitByBoneCount(aqo, tempModel, boneLimit); aqo.tempTris = tempModel.tempTris; aqo.vtxlList = tempModel.vtxlList; tempModel = null; AquaObjectMethods.RemoveAllUnusedBones(aqo); } //AquaObjectMethods.CalcUNRMs(aqo, aqo.applyNormalAveraging, aqo.objc.unrmOffset != 0); //Set up PSETs and strips, and other per mesh data for (int i = 0; i < aqo.tempTris.Count; i++) { //strips AquaObject.stripData strips; if (aqo.objc.type >= 0xC32) { strips = new AquaObject.stripData(); strips.format0xC33 = true; strips.triStrips = new List <ushort>(aqo.tempTris[i].toUshortArray()); strips.triIdCount = strips.triStrips.Count; strips.faceGroups.Add(strips.triStrips.Count); } else { strips = new AquaObject.stripData(aqo.tempTris[i].toUshortArray()); } aqo.strips.Add(strips); //PSET var pset = new AquaObject.PSET(); pset.faceGroupCount = 0x1; pset.psetFaceCount = strips.triIdCount; if (aqo.objc.type >= 0xC32) { pset.tag = 0x1000; pset.stripStartCount = totalStripsShorts; } else { pset.tag = 0x2100; } aqo.psetList.Add(pset); totalStripsShorts += strips.triIdCount; //Update this *after* setting the strip start count so that we don't direct to bad data. //MESH Match m; int idx_MATE = 0; int idx_REND = 0; int idx_SHAD = 0; int idx_TSET = 0; if (null == aqo.tempTris[i].name) { m = null; } else { m = RE_ObjName.Match(aqo.tempTris[i].name); if (!m.Success) { m = null; } else { idx_MATE = int.Parse(m.Groups[1].Value); idx_REND = int.Parse(m.Groups[2].Value); idx_SHAD = int.Parse(m.Groups[3].Value); idx_TSET = int.Parse(m.Groups[4].Value); } } if (m == null) { idx_MATE = oldMESHList[0].mateIndex; idx_REND = oldMESHList[0].rendIndex; idx_SHAD = oldMESHList[0].shadIndex; idx_TSET = oldMESHList[0].tsetIndex; } var mesh = new AquaObject.MESH(); AquaObject.MESH oldMesh = new AquaObject.MESH(); bool oldMeshFound = false; //Compare for (int msh = 0; msh < oldMESHList.Count; msh++) { var tempMesh = oldMESHList[msh]; if (tempMesh.mateIndex == idx_MATE && tempMesh.rendIndex == idx_REND && tempMesh.shadIndex == idx_SHAD && tempMesh.tsetIndex == idx_TSET) { oldMesh = tempMesh; oldMeshFound = true; break; } } if (oldMeshFound == false) { mesh.flags = 0x17; //No idea what this really does. Seems to vary a lot, but also not matter a lot. mesh.unkShort0 = 0x0; mesh.unkByte0 = 0x80; mesh.unkByte1 = 0x64; mesh.unkShort1 = 0; mesh.mateIndex = idx_MATE; mesh.rendIndex = idx_REND; mesh.shadIndex = idx_SHAD; mesh.tsetIndex = idx_TSET; mesh.baseMeshNodeId = 0; mesh.baseMeshDummyId = 0; mesh.unkInt0 = 0; } else { mesh.flags = oldMesh.flags; mesh.unkShort0 = oldMesh.unkShort0; mesh.unkByte0 = oldMesh.unkByte0; mesh.unkByte1 = oldMesh.unkByte1; mesh.unkShort1 = oldMesh.unkShort1; mesh.mateIndex = idx_MATE; mesh.rendIndex = idx_REND; mesh.shadIndex = idx_SHAD; mesh.tsetIndex = idx_TSET; mesh.baseMeshNodeId = oldMesh.baseMeshNodeId; mesh.baseMeshDummyId = oldMesh.baseMeshDummyId; mesh.unkInt0 = oldMesh.unkInt0; } mesh.vsetIndex = i; mesh.psetIndex = i; mesh.reserve0 = 0; aqo.meshList.Add(mesh); } //Generate VTXEs and VSETs int largestVertSize = 0; int vertCounter = 0; totalVerts = 0; aqo.vsetList.Clear(); aqo.vtxeList.Clear(); for (int i = 0; i < aqo.vtxlList.Count; i++) { totalVerts += aqo.vtxlList[i].vertPositions.Count; AquaObject.VTXE vtxe = AquaObjectMethods.ConstructClassicVTXE(aqo.vtxlList[i], out int size); aqo.vtxeList.Add(vtxe); //Track this for objc if (size > largestVertSize) { largestVertSize = size; } AquaObject.VSET vset = new AquaObject.VSET(); vset.vertDataSize = size; vset.vtxlCount = aqo.vtxlList[i].vertPositions.Count; vset.edgeVertsCount = aqo.vtxlList[i].edgeVerts.Count; if (aqo.objc.type >= 0xC32) { vset.vtxeCount = aqo.vtxeList.Count - 1; vset.vtxlStartVert = vertCounter; vertCounter += vset.vtxlCount; vset.bonePaletteCount = -1; } else { vset.vtxeCount = vtxe.vertDataTypes.Count; vset.bonePaletteCount = aqo.vtxlList[i].bonePalette.Count; } aqo.vsetList.Add(vset); } //Update OBJC aqo.objc.largetsVtxl = largestVertSize; aqo.objc.totalStripFaces = totalStripsShorts; aqo.objc.totalVTXLCount = totalVerts; aqo.objc.unkStructCount = aqo.vtxlList.Count; aqo.objc.vsetCount = aqo.vsetList.Count; aqo.objc.psetCount = aqo.psetList.Count; aqo.objc.meshCount = aqo.meshList.Count; aqo.objc.mateCount = aqo.mateList.Count; aqo.objc.rendCount = aqo.rendList.Count; aqo.objc.shadCount = aqo.shadList.Count; aqo.objc.tstaCount = aqo.tstaList.Count; aqo.objc.tsetCount = aqo.tsetList.Count; aqo.objc.texfCount = aqo.texfList.Count; aqo.objc.vtxeCount = aqo.vtxeList.Count; aqo.objc.fBlock0 = -1; aqo.objc.fBlock1 = -1; aqo.objc.fBlock2 = -1; aqo.objc.fBlock3 = -1; aqo.objc.globalStrip3LengthCount = 1; aqo.objc.unkCount3 = 1; aqo.objc.bounds = AquaObjectMethods.GenerateBounding(aqo.vtxlList); if (doBitangent) { AquaObjectMethods.ComputeTangentSpace(aqo, false, true); } return(aqo); }
public ObjMesh(ObjFile owner) { Owner = owner; SubMeshes = new List <ObjSubMesh>(); }