import aliases; import includes; import std.stdio; import std.string; import std.file; import util; import std.path; import assets; import std.traits; import std.algorithm.comparison; import core.memory; import std.json; AssetType[string] Lookup = [ ".m3d": AT.ModelM3D, ".png": AT.Texture, ".jpg": AT.Texture, ".spv": AT.Shader, ]; u64 ASSET_COUNT = 0; string[] FILE_NAMES = []; /************************************************** ****** UPDATE FILE_VERSION AFTER CHANGES !! ****** **************************************************/ void main(string[] argv) { Log("running"); if (isDir("build")) { chdir("build"); } bool pack = false; foreach(arg; argv) { if (arg == "pack") { pack = true; } } if (pack) { PackFile(); TestFile(); } CodegenFontLookup(); } void CodegenFontLookup() { mkdirRecurse("../src/codegen"); auto f = File("../src/codegen/fonts.d", "w"); string font_json = readText("atlas.json"); JSONValue j = parseJSON(font_json); assert("atlas" in j, "atlas key not in json"); FontAtlas atlas = { type: (j["atlas"]["type"].str == "softmask" ? AtlasType.SoftMask : AtlasType.None), size: j["atlas"]["size"].floating, width: cast(u32)j["atlas"]["width"].integer, height: cast(u32)j["atlas"]["height"].integer, y_origin: (j["atlas"]["yOrigin"].str == "bottom" ? YOrigin.Bottom : YOrigin.None), em_size: cast(f32)j["metrics"]["emSize"].integer, line_height: j["metrics"]["lineHeight"].floating, ascender: j["metrics"]["ascender"].floating, descender: j["metrics"]["descender"].floating, underline_y: j["metrics"]["underlineY"].floating, underline_thickness: j["metrics"]["underlineThickness"].floating, }; foreach(val; j["glyphs"].array) { Glyph glyph = { ch: cast(dchar)val["unicode"].integer, advance: val["advance"].floating, }; if ("planeBounds" in val) { glyph.plane_left = val["planeBounds"]["left"].floating; glyph.plane_bottom = val["planeBounds"]["bottom"].floating; glyph.plane_right = val["planeBounds"]["right"].floating; glyph.plane_top = val["planeBounds"]["top"].floating; glyph.atlas_left = val["atlasBounds"]["left"].floating; glyph.atlas_bottom = val["atlasBounds"]["bottom"].floating; glyph.atlas_right = val["atlasBounds"]["right"].floating; glyph.atlas_top = val["atlasBounds"]["top"].floating; } atlas.glyphs ~= glyph; } f.writeln("import util;\n"); f.writeln("static immutable FontAtlas FONT_ATLAS = {"); f.writefln("\ttype: AtlasType.%s,", atlas.type); f.writefln("\tsize: %f,", atlas.size); f.writefln("\twidth: %d,", atlas.width); f.writefln("\theight: %d,", atlas.height); f.writefln("\ty_origin: YOrigin.%s,", atlas.y_origin); f.writefln("\tem_size: %f,", atlas.em_size); f.writefln("\tline_height: %f,", atlas.line_height); f.writefln("\tascender: %f,", atlas.ascender); f.writefln("\tdescender: %f,", atlas.descender); f.writefln("\tunderline_y: %f,", atlas.underline_y); f.writefln("\tunderline_thickness: %f,", atlas.underline_thickness); f.writeln("\tglyphs: ["); foreach(g; atlas.glyphs) { f.writeln("\t\t{"); f.writefln("\t\t\tch: '%s',", g.ch); f.writefln("\t\t\tadvance: %f,", g.advance); if (g.plane_left > 0.0 || g.plane_right > 0.0) { f.writefln("\t\t\tplane_left: %f,", g.plane_left); f.writefln("\t\t\tplane_bottom: %f,", g.plane_bottom); f.writefln("\t\t\tplane_right: %f,", g.plane_right); f.writefln("\t\t\tplane_top: %f,", g.plane_top); f.writefln("\t\t\tatlas_left: %f,", g.atlas_left); f.writefln("\t\t\tatlas_bottom: %f,", atlas.height - g.atlas_bottom); f.writefln("\t\t\tatlas_right: %f,", g.atlas_right); f.writefln("\t\t\tatlas_top: %f,", atlas.height - g.atlas_top); } f.writeln("\t\t},"); } f.writeln("\t],"); f.writeln("};"); } void PackFile() { File ap = File("./assets.sgp", "wb"); scope(exit) { ap.flush(); ap.close(); chdir("../build"); } chdir("../assets"); foreach(string file; dirEntries(".", SpanMode.depth)) { if (isDir(file)) continue; FILE_NAMES ~= file; ASSET_COUNT += 1; } FileHeader h = InitHeader(ASSET_COUNT); ap.rawWrite([h]); u64 offset = FileHeader.sizeof + (AssetInfo.sizeof * ASSET_COUNT); AssetInfo[] asset_info; foreach(file; FILE_NAMES) { AssetType type = AT.None; foreach(extension, t; Lookup) { if (file.endsWith(extension)) { type = t; break; } } assert(type != AT.None, "Asset Type is none, offending file " ~ file); auto f = File(file, "rb"); scope(exit) f.close(); u64 length = cast(u64)f.size(); string base_name = chompPrefix(file, "./"); AssetInfo info = { hash: Hash(base_name), offset: offset, length: length, type: type, }; auto data = f.rawRead(new u8[length]); assert(length == data.length, "rawRead failure: data length returned doesn't match"); ap.seek(offset); ap.rawWrite(data); offset += length; asset_info ~= info; } ap.seek(FileHeader.sizeof); ap.rawWrite(asset_info); } TexMeta GetTexMeta(u8[] data) { int ch = 4; int width, height, has_ch; auto img = stbi_load_from_memory(data.ptr, cast(int)data.length, &width, &height, &has_ch, ch); assert(img != null, "stbi_load_from_image failure: image is NULL"); assert(width > 0 && height > 0 && has_ch > 0, "stbi_load_from_image failure: dimensions are invalid"); stbi_image_free(img); return TexMeta(w: width, h: height, ch: has_ch); } ModelMeta GetModelMeta(u8[] data) { m3d_t* model = m3d_load(data.ptr, null, null, null); assert(model != null, "m3d_load failure: model is null"); u64 index_count = cast(u64)(model.numface * 3); m3d_free(model); return ModelMeta(index_count: index_count); } void TestFile() { File ap = File("assets.sgp", "rb"); scope(exit) { ap.flush(); ap.close(); chdir("../build"); } FileHeader file_header = ap.rawRead(new FileHeader[1])[0]; FileHeader test_header = InitHeader(ASSET_COUNT); assert(file_header == test_header, "TestFile failure: Header is incorrect"); AssetInfo[] file_info = ap.rawRead(new AssetInfo[ASSET_COUNT]); assert(file_info.length == file_header.asset_count, "TestFile failure: Incorrect AssetInfo length returned"); chdir("../assets"); u64 asset_index = 0; foreach(i, file; FILE_NAMES) { scope(exit) asset_index += 1; AssetInfo* info = file_info.ptr + asset_index; File asset = File(file, "rb"); u8[] data = asset.rawRead(new u8[asset.size()]); assert(data.length == info.length, "TestFile failure: File length read is incorrect"); string base_name = chompPrefix(file, "./"); assert(Hash(base_name) == info.hash, "TestFile failure: File hash is incorrect"); ap.seek(info.offset); u8[] pack_data = ap.rawRead(new u8[info.length]); assert(equal!((a, b) => a == b)(data[], pack_data[]), "TestFile failure: Asset data does not match file data"); } } static u32 MagicValue(string str) { assert(str.length == 4, "Magic value must 4 characters"); return cast(u32)(cast(u32)(str[0] << 24) | cast(u32)(str[1] << 16) | cast(u32)(str[2] << 8) | cast(u32)(str[3] << 0)); } static FileHeader InitHeader(u64 asset_count) { FileHeader header = { magic: MagicValue("steg"), file_version: FILE_VERSION, asset_count: asset_count, asset_info_offset: FileHeader.sizeof, }; return header; } static ModelHeader InitModelHeader() { ModelHeader header = { magic: MagicValue("stgm"), model_version: MODEL_VERSION, }; return header; } ModelData ConvertModel(u8[] data) { assert(data.length > 4, "Model data too small for a magic number"); u32 magic = *cast(u32*)(data.ptr); ModelData model; switch (magic) { case MagicValue("3DMO"): { model = ConvertM3D(data); } break; case MagicValue("glTF"): { model = ConvertGLTF(data); } break; default: { assert(false, "Unsupported model type"); } break; } return model; } ModelData ConvertGLTF(u8[] data) { ModelData model; assert(false, "Not yet supported"); return model; } ModelData ConvertM3D(u8[] data) { ModelData model; return model; }