private IEnumerable <Bar.Entry> LoadAdditionalBarEntries(MapGenConfig config, Func <string, byte[]> fileLoader) { foreach (var addFile in config.addFiles) { var data = fileLoader(addFile.fromFile); yield return(new Bar.Entry { Name = addFile.name, Type = (Bar.EntryType)addFile.type, Stream = new MemoryStream(data), Index = addFile.index, }); } }
private Imgd FileLoader(string filePath, MaterialDef matDef, MapGenConfig config) { logger.Debug($"Load image from \"{filePath}\""); if (FileExtUtil.IsExtension(filePath, ".imd")) { return(ImageResizer.NormalizeImageSize(File.OpenRead(filePath).Using(s => Imgd.Read(s)))); } if (FileExtUtil.IsExtension(filePath, ".png")) { var imdFile = Path.ChangeExtension(filePath, ".imd"); if (config.skipConversionIfExists && File.Exists(imdFile)) { logger.Debug($"Skipping png to imd conversion, due to imd file existence and skipConversionIfExists option."); } else { try { logger.Debug($"Using ImgTool for png to imd conversion."); var imgtoolOptions = matDef.imgtoolOptions ?? config.imgtoolOptions ?? "-b 8"; var result = new RunCmd( "OpenKh.Command.ImgTool.exe", $"imd \"{filePath}\" -o \"{imdFile}\" {imgtoolOptions}" ); if (result.ExitCode != 0) { throw new Exception($"ImgTool failed ({result.ExitCode})"); } } catch (Win32Exception ex) { throw new Exception("ImgTool failed.", ex); } } return(FileLoader(imdFile, matDef, config)); } throw new NotSupportedException(Path.GetExtension(filePath)); }
private void ConvertModelIntoMapModel(string modelFile, MapGenConfig config) { logger.Debug($"Loading 3D model file \"{modelFile}\" using Assimp."); var assimp = new Assimp.AssimpContext(); var scene = assimp.ImportFile(modelFile, Assimp.PostProcessSteps.PreTransformVertices); bigMeshContainer = new BigMeshContainer(); var scale = config.scale; Matrix4x4 matrix = Matrix4x4.Identity; if (config.applyMatrix != null) { var m = config.applyMatrix; if (m.Length == 16) { matrix = new Matrix4x4( m[0], m[1], m[2], m[3], m[4], m[5], m[6], m[7], m[8], m[9], m[10], m[11], m[12], m[13], m[14], m[15] ); logger.Debug($"Apply matrix: {matrix}"); } } else { matrix *= scale; } logger.Debug($"Starting triangle strip conversion for {scene.Meshes.Count} meshes."); foreach (var inputMesh in scene.Meshes) { logger.Debug($"Mesh: {inputMesh.Name} ({inputMesh.FaceCount:#,##0} faces, {inputMesh.VertexCount:#,##0} vertices)"); var modelMat = scene.Materials[inputMesh.MaterialIndex]; var matDef = config.FindMaterial(modelMat.Name ?? "default") ?? MaterialDef.CreateFallbackFor(modelMat.Name); if (matDef.ignore) { logger.Info($"This mesh \"{inputMesh.Name}\" is not rendered due to ignore flag of material \"{modelMat.Name}\"."); continue; } var kh2Mesh = bigMeshContainer.AllocateBigMeshForMaterial(matDef); var diffuseTextureFile = modelMat.TextureDiffuse.FilePath; if (!string.IsNullOrEmpty(diffuseTextureFile)) { if (config.reuseImd) { logger.Debug($"The mesh \"{inputMesh.Name}\" material \"{matDef.name}\" has filepath \"{diffuseTextureFile}\" for diffuse texture. It will be associated with material's fromFile3. Setting preferable imd file to fromFile2 due to reuseImd flag."); matDef.fromFile2 = Path.ChangeExtension(diffuseTextureFile, ".imd"); matDef.fromFile3 = diffuseTextureFile; } else { logger.Debug($"The mesh \"{inputMesh.Name}\" material \"{matDef.name}\" has filepath \"{diffuseTextureFile}\" for diffuse texture. It will be associated with material's fromFile2."); matDef.fromFile3 = diffuseTextureFile; } } var kh2BaseVert = kh2Mesh.vertexList.Count; List <int> vertexToLocal = new List <int>(); foreach (var inputVertex in inputMesh.Vertices) { var vertex = Vector3.Transform( new Vector3(inputVertex.X, inputVertex.Y, inputVertex.Z), matrix ); var index = kh2Mesh.vertexList.IndexOf(vertex); if (index < 0) { index = kh2Mesh.vertexList.Count; kh2Mesh.vertexList.Add(vertex); } vertexToLocal.Add(index); } var localFaces = inputMesh.Faces .Select( set => set.Indices .Select(index => new VertPair { uvColorIndex = index, vertexIndex = vertexToLocal[index] }) .ToArray() ) .ToArray(); var inputTexCoords = inputMesh.TextureCoordinateChannels.First(); var inputVertexColorList = inputMesh.VertexColorChannels.First(); var hasVertexColor = inputMesh.VertexColorChannelCount >= 1; var maxIntensity = matDef.maxColorIntensity ?? config.maxColorIntensity ?? 128; var maxAlpha = matDef.maxAlpha ?? config.maxAlpha ?? 128; var triConverter = config.disableTriangleStripsOptimization ? (TriangleFansToTriangleStripsConverter)TriangleFansToTriangleStripsNoOpts : (TriangleFansToTriangleStripsConverter)TriangleFansToTriangleStripsOptimized; foreach (var triStripInput in triConverter(localFaces)) { var triStripOut = new BigMesh.TriangleStrip(); foreach (var vertPair in triStripInput) { triStripOut.vertexIndices.Add(kh2BaseVert + vertPair.vertexIndex); triStripOut.uvList.Add(Get2DCoord(inputTexCoords[vertPair.uvColorIndex])); if (hasVertexColor) { triStripOut.vertexColorList.Add(ConvertVertexColor(inputVertexColorList[vertPair.uvColorIndex], maxIntensity, maxAlpha)); } else { triStripOut.vertexColorList.Add(new Color(maxIntensity, maxIntensity, maxIntensity, maxAlpha)); } } kh2Mesh.triangleStripList.Add(triStripOut); } logger.Debug($"Output: {kh2Mesh.vertexList.Count:#,##0} vertices, {kh2Mesh.triangleStripList.Count:#,##0} triangle strips."); } logger.Debug($"The conversion has done."); logger.Debug($"Starting mesh splitter and vif packets builder."); mapModel = new Mdlx.M4 { VifPackets = new List <Mdlx.VifPacketDescriptor>(), }; foreach (var bigMesh in bigMeshContainer.MeshList .Where(it => it.textureIndex != -1) ) { foreach (var smallMesh in BigMeshSplitter.Split(bigMesh)) { var dmaPack = new MapVifPacketBuilder(smallMesh); smallMeshList.Add(smallMesh); bigMesh.vifPacketIndices.Add(Convert.ToUInt16(mapModel.VifPackets.Count)); smallMesh.vifPacketIndices.Add(Convert.ToUInt16(mapModel.VifPackets.Count)); mapModel.VifPackets.Add( new Mdlx.VifPacketDescriptor { VifPacket = dmaPack.vifPacket.ToArray(), TextureId = smallMesh.textureIndex, DmaPerVif = new ushort[] { dmaPack.firstVifPacketQwc, 0, }, IsTransparentFlag = smallMesh.matDef.transparentFlag ?? 0, } ); } } logger.Debug($"Output: {mapModel.VifPackets.Count:#,##0} vif packets."); logger.Debug($"The builder has done."); logger.Debug($"Starting vifPacketRenderingGroup builder."); // first group: render all mapModel.vifPacketRenderingGroup = new List <ushort[]>( new ushort[][] { Enumerable.Range(0, mapModel.VifPackets.Count) .Select(it => Convert.ToUInt16(it)) .ToArray() } ); logger.Debug($"Output: {mapModel.vifPacketRenderingGroup.Count:#,##0} groups."); mapModel.DmaChainIndexRemapTable = new List <ushort>( Enumerable.Range(0, mapModel.VifPackets.Count) .Select(it => Convert.ToUInt16(it)) .ToArray() ); }
public MapBuilder(string modelFile, MapGenConfig config, Func <MaterialDef, Imgd> imageLoader) { this.config = config; ConvertModelIntoMapModel(modelFile, config); logger.Debug($"Starting collision plane builder."); collisionBuilder = new CollisionBuilder(smallMeshList, config.disableBSPCollisionBuilder); if (collisionBuilder.vifPacketRenderingGroup != null) { logger.Debug($"Updating vifPacketRenderingGroup builder."); mapModel.vifPacketRenderingGroup = collisionBuilder.vifPacketRenderingGroup; logger.Debug($"Output: {mapModel.vifPacketRenderingGroup.Count:#,##0} groups."); } logger.Debug($"Output: {collisionBuilder.coct.CollisionMeshGroupList.Count:#,##0} collision mesh groups"); { var matDefList = bigMeshContainer.AllocatedMaterialDefs; var imageSets = matDefList .Select(matDef => new ImageSet { image = imageLoader(matDef), matDef = matDef, }) .ToArray(); var build = new ModelTexture.Build { images = imageSets .Select(set => set.image) .ToArray(), offsetData = null, // auto serial number map textureTransfer = null, // auto create gsInfo = imageSets .Select( set => { var gsInfo = new ModelTexture.UserGsInfo(set.image); gsInfo.AddressMode.AddressU = Enum.Parse <ModelTexture.TextureWrapMode>( set.matDef.textureOptions.addressU ?? config.textureOptions.addressU ?? "Repeat" ); gsInfo.AddressMode.AddressV = Enum.Parse <ModelTexture.TextureWrapMode>( set.matDef.textureOptions.addressV ?? config.textureOptions.addressV ?? "Repeat" ); return(gsInfo); } ) .ToArray(), }; modelTex = new ModelTexture(build); } }
private Func <MaterialDef, Imgd> CreateImageLoader(string baseDir, MapGenConfig config, Func <string, MaterialDef, MapGenConfig, Imgd> fileLoader) { return((matDef) => { logger.Debug($"Going to load material \"{matDef.name}\"."); var fileNames = new string[] { matDef.fromFile, matDef.fromFile2, matDef.fromFile3, matDef.name + ".imd", matDef.name + ".png", } .Where(it => !string.IsNullOrWhiteSpace(it)) .ToList(); var allImageDirs = new string[] { } .Concat( (config.imageDirs ?? new string[0]) .Select(imageDir => Path.Combine(baseDir, imageDir)) ) .Concat( new string[] { baseDir } ) .ToArray(); var pathList = allImageDirs .SelectMany( imageDir => fileNames.Select(fileName => Path.Combine(imageDir, fileName)) ) .ToArray(); var found = pathList .FirstOrDefault(File.Exists); Imgd loaded = null; if (found != null && File.Exists(found)) { try { loaded = fileLoader(found, matDef, config); } catch (Exception ex) { logger.Warn(ex, "Load image failed!"); } } if (loaded == null) { logger.Warn($"File not found of material \"{matDef.name}\", or error thrown. Using fallback image instead."); // fallback var pixels = new byte[128 * 128]; for (int x = 0; x < pixels.Length; x++) { var y = x / 128; var set = 0 != ((1 & (x / 8)) ^ (1 & (y / 8))); pixels[x] = (byte)(set ? 255 : 95); } var palette = new byte[4 * 256]; for (int x = 0; x < 256; x++) { var v = (byte)x; palette[4 * x + 0] = v; palette[4 * x + 1] = v; palette[4 * x + 2] = v; palette[4 * x + 3] = 255; } return Imgd.Create( new Size(128, 128), Imaging.PixelFormat.Indexed8, pixels, palette, false ); } return loaded; }); }