#include "AssetManager.hpp" #include #include #include #include #include #include #define STBI_NO_STDIO #define STB_IMAGE_IMPLEMENTATION #define STBI_ONLY_PNG #include #include #include "Utility.hpp" #include "Plugin.hpp" namespace fs = std::filesystem; const fs::path pathToAssets = "./assets/"; const std::string pathToAssetsList = "./items.json"; std::map blockIdToBlockName; std::unique_ptr assetTree; std::unique_ptr atlas; std::map blockIdToBlockFaces; BlockFaces errorFaces; void LoadAssets(); void LoadTextures(); void LoadScripts(); void WalkDirEntry(const fs::directory_entry &dirEntry, AssetTreeNode *node); void ParseAsset(AssetTreeNode &node); void ParseAssetTexture(AssetTreeNode &node); void ParseAssetBlockModel(AssetTreeNode &node); void ParseAssetBlockState(AssetTreeNode &node); void ParseAssetShader(AssetTreeNode &node); void ParseAssetScript(AssetTreeNode &node); void ParseBlockModels(); void AssetManager::InitAssetManager() { static bool Initialized = false; if (Initialized) { LOG(WARNING) << "Trying to init AssetManager twice"; } Initialized = true; LoadAssets(); auto parseAssetRecur = [](AssetTreeNode &node) { ParseAsset(node); }; RecursiveWalkAsset("/", parseAssetRecur); LoadTextures(); ParseBlockModels(); PluginSystem::Init(); } void AssetManager::InitPostRml() { LoadScripts(); errorFaces.transform = glm::mat4(1.0); errorFaces.faces = GetAsset("/minecraft/models/block/error")->blockModel.parsedFaces; errorFaces.isBlock = GetAsset("/minecraft/models/block/error")->blockModel.IsBlock; for (int i = 0; i < FaceDirection::none; i++) { errorFaces.faceDirectionVector[i] = FaceDirectionVector[i]; } } void LoadAssets() { assetTree = std::make_unique(); assetTree->name = "/"; WalkDirEntry(fs::directory_entry(pathToAssets), assetTree.get()); } void LoadTextures() { std::vector textureData; size_t id = 0; AssetManager::RecursiveWalkAsset("/minecraft/textures/", [&](AssetTreeNode &node) { TextureData data; AssetTexture *textureAsset = dynamic_cast(node.asset.get()); if (!textureAsset) return; data.data = std::move(textureAsset->textureData); data.width = textureAsset->realWidth; data.height = textureAsset->realHeight; textureData.push_back(data); textureAsset->id = id++; }); atlas = std::make_unique(textureData); } void LoadScripts() { AssetTreeNode *node = AssetManager::GetAssetByAssetName("/"); for (auto &it : node->childs) { for (auto &child : it->childs) { if (child->name == "scripts") { for (auto &script : child->childs) { if (script->name != "init") continue; AssetScript *asset = dynamic_cast(script->asset.get()); if (!asset) { LOG(ERROR) << "Unrecognised script file /" << it->name; continue; } try { PluginSystem::Execute(asset->code, true); } catch (std::exception & e) { LOG(ERROR) << "Failed loading script '" << script->name << "' in '" << it->name << "'"; } break; } } } } LOG(INFO) << "Scripts loaded"; } void WalkDirEntry(const fs::directory_entry& dirEntry, AssetTreeNode* node) { for (auto& file : fs::directory_iterator(dirEntry)) { node->childs.push_back(std::make_unique()); AssetTreeNode* fileNode = node->childs.back().get(); fileNode->parent = node; fileNode->name = file.path().stem().string(); if (fs::is_directory(file)) { WalkDirEntry(file, fileNode); } else { size_t fileSize = fs::file_size(file); fileNode->data.resize(fileSize); FILE* f = fopen(file.path().string().c_str(), "rb"); if (f) { fread(fileNode->data.data(), 1, fileSize, f); fclose(f); } else LOG(WARNING) << "Can't open asset file " << file.path().string(); } } } void ParseAsset(AssetTreeNode &node) { if (node.data.empty() || node.asset) return; if (node.parent->name == "block" && node.parent->parent->name == "models") { ParseAssetBlockModel(node); return; } if (node.parent->name == "blockstates") { ParseAssetBlockState(node); return; } if (node.data[0] == 0x89 && node.data[1] == 'P' && node.data[2] == 'N' && node.data[3] == 'G') { ParseAssetTexture(node); return; } if (node.parent->name == "shaders") { ParseAssetShader(node); return; } if (node.parent->name == "scripts") { ParseAssetScript(node); return; } } void ParseAssetTexture(AssetTreeNode &node) { int w, h, n; unsigned char *data = stbi_load_from_memory(node.data.data(),node.data.size(), &w, &h, &n, 4); if (data == nullptr) { return; } node.asset = std::make_unique(); AssetTexture *asset = dynamic_cast(node.asset.get()); size_t dataLen = w * h * 4; if (asset) asset->textureData.resize(dataLen); std::memcpy(asset->textureData.data(), data, dataLen); asset->realWidth = w; asset->realHeight = h; bool foundAnimationFile = false; for (const auto &it : node.parent->childs) if (it->name == node.name + ".png") { foundAnimationFile = true; break; } asset->frames = foundAnimationFile ? _max(w, h) / _min(w, h) : 1; stbi_image_free(data); node.data.clear(); node.data.shrink_to_fit(); } void ParseAssetBlockModel(AssetTreeNode &node) { nlohmann::json modelData = nlohmann::json::parse(node.data); BlockModel model; if (node.name == "button") { int a = 15; a++; } if (modelData.find("parent") != modelData.end()) { std::string parentName = modelData["parent"].get(); parentName = parentName.substr(parentName.find('/') + 1); for (auto &it : node.parent->childs) { if (it->name == parentName) { ParseAsset(*it); model = dynamic_cast(it->asset.get())->blockModel; unsigned char *b = reinterpret_cast(&model.IsBlock); } } } model.BlockName = node.name; if (model.BlockName == "block" || model.BlockName == "cube_mirrored") model.IsBlock = true; if (model.BlockName == "thin_block" || model.BlockName == "leaves") model.IsBlock = false; if (modelData.find("ambientocclusion") != modelData.end()) model.AmbientOcclusion = modelData["ambientocclusion"].get(); //models.Display if (modelData.find("textures") != modelData.end()) { for (nlohmann::json::iterator texture = modelData["textures"].begin(); texture != modelData["textures"].end(); ++texture) { model.Textures[texture.key()] = texture.value().get(); } } if (modelData.find("elements") != modelData.end()) { model.Elements.clear(); for (auto& it : modelData["elements"]) { BlockModel::ElementData element; auto vec = it["from"]; Vector from(vec[0].get(), vec[1].get(), vec[2].get()); vec = it["to"]; Vector to(vec[0].get(), vec[1].get(), vec[2].get()); element.from = from; element.to = to; if (it.find("rotation") != it.end()) { vec = it["rotation"]["origin"]; Vector rotOrig(vec[0].get(), vec[1].get(), vec[2].get()); element.rotationOrigin = rotOrig; element.rotationAxis = (it["rotation"]["axis"].get() == "x") ? BlockModel::ElementData::Axis::x : ((it["rotation"]["axis"].get() == "y") ? BlockModel::ElementData::Axis::y : BlockModel::ElementData::Axis::z); if (it["rotation"].find("angle") != it["rotation"].end()) element.rotationAngle = it["rotation"]["angle"].get(); if (it["rotation"].find("rescale") != it["rotation"].end()) element.rotationRescale = it["rotation"]["rescale"].get(); } if (it.find("shade") != it.end()) element.shade = it["shade"].get(); for (nlohmann::json::iterator faceIt = it["faces"].begin(); faceIt != it["faces"].end(); ++faceIt) { auto face = faceIt.value(); BlockModel::ElementData::FaceData faceData; FaceDirection faceDir = FaceDirection::none; if (faceIt.key() == "down") faceDir = FaceDirection::down; else if (faceIt.key() == "up") faceDir = FaceDirection::up; else if (faceIt.key() == "north") faceDir = FaceDirection::north; else if (faceIt.key() == "south") faceDir = FaceDirection::south; else if (faceIt.key() == "west") faceDir = FaceDirection::west; else if (faceIt.key() == "east") faceDir = FaceDirection::east; if (face.find("uv") != face.end()) { BlockModel::ElementData::FaceData::Uv uv; uv.x1 = face["uv"][0]; uv.y1 = face["uv"][1]; uv.x2 = face["uv"][2]; uv.y2 = face["uv"][3]; faceData.uv = uv; } FaceDirection cullface = FaceDirection::none; if (face.find("cullface") != face.end()) { if (face["cullface"] == "down") cullface = FaceDirection::down; else if (face["cullface"] == "up") cullface = FaceDirection::up; else if (face["cullface"] == "north") cullface = FaceDirection::north; else if (face["cullface"] == "south") cullface = FaceDirection::south; else if (face["cullface"] == "west") cullface = FaceDirection::west; else if (face["cullface"] == "east") cullface = FaceDirection::east; } faceData.cullface = cullface; faceData.texture = face["texture"].get(); if (face.find("rotation") != face.end()) faceData.rotation = face["rotation"].get(); if (face.find("tintindex") != face.end()) faceData.tintIndex = true; element.faces[faceDir] = faceData; } model.Elements.push_back(element); } } node.asset = std::make_unique(); if (node.asset) dynamic_cast(node.asset.get())->blockModel = model; node.data.clear(); node.data.shrink_to_fit(); } void ParseAssetBlockState(AssetTreeNode &node) { nlohmann::json j = nlohmann::json::parse(node.data); BlockState blockState; if (j.find("multipart") != j.end()) return; j = j["variants"]; for (auto variantIt = j.begin(); variantIt != j.end(); variantIt++) { BlockStateVariant variant; variant.variantName = variantIt.key(); const auto& variantValue = variantIt.value(); if (variantValue.is_array()) { for (auto &it : variantValue) { BlockStateVariant::Model model; model.modelName = it["model"].get(); if (it.count("x")) model.x = it["x"].get(); if (it.count("y")) model.y = it["y"].get(); if (it.count("uvlock")) model.uvLock = it["uvlock"].get(); if (it.count("weight")) model.weight = it["weight"].get(); variant.models.push_back(model); } } else { BlockStateVariant::Model model; model.modelName = variantValue["model"].get(); if (variantValue.count("x")) model.x = variantValue["x"].get(); if (variantValue.count("y")) model.y = variantValue["y"].get(); if (variantValue.count("uvlock")) model.uvLock = variantValue["uvlock"].get(); if (variantValue.count("weight")) model.weight = variantValue["weight"].get(); variant.models.push_back(model); } blockState.variants[variant.variantName] = variant; } node.asset = std::make_unique(); AssetBlockState *asset = dynamic_cast(node.asset.get()); if (asset) asset->blockState = blockState; node.data.clear(); node.data.shrink_to_fit(); } void ParseAssetShader(AssetTreeNode &node) { } void ParseAssetScript(AssetTreeNode &node) { node.asset = std::make_unique(); AssetScript *asset = dynamic_cast(node.asset.get()); if (asset) asset->code = std::string((char*)node.data.data(), (char*)node.data.data() + node.data.size()); } void ParseBlockModels() { std::string textureName; auto parseBlockModel = [&](AssetTreeNode &node) { if (!node.asset) return; if (!dynamic_cast(node.asset.get())) return; BlockModel &model = dynamic_cast(node.asset.get())->blockModel; for (const auto& element : model.Elements) { Vector t = element.to - element.from; VectorF elementSize(VectorF(t.x, t.y, t.z) / 16.0f); VectorF elementOrigin(VectorF(element.from.x, element.from.y, element.from.z) / 16.0f); glm::mat4 elementTransform = glm::mat4(1.0); if (element.rotationAngle != 0) { static const glm::vec3 xAxis(1.0f, 0.0f, 0.0f); static const glm::vec3 yAxis(0.0f, 1.0f, 0.0f); static const glm::vec3 zAxis(0.0f, 0.0f, 1.0f); const glm::vec3 *targetAxis = &xAxis; switch (element.rotationAxis) { case BlockModel::ElementData::Axis::x: targetAxis = &xAxis; break; case BlockModel::ElementData::Axis::y: targetAxis = &yAxis; break; case BlockModel::ElementData::Axis::z: targetAxis = &zAxis; break; } VectorF rotateOrigin(VectorF(element.rotationOrigin.x, element.rotationOrigin.y, element.rotationOrigin.z) / 16.0f); glm::mat4 rotationMat = glm::mat4(1.0); rotationMat = glm::translate(rotationMat, rotateOrigin.glm()); rotationMat = glm::rotate(rotationMat, glm::radians((float)element.rotationAngle), *targetAxis); if (element.rotationRescale) { glm::vec3 scaleFactor{ 1.0f,1.0f,1.0f }; double coef = 1.0f / cos(glm::radians((double)element.rotationAngle)); switch (element.rotationAxis) { case BlockModel::ElementData::Axis::x: scaleFactor.y *= coef; scaleFactor.z *= coef; break; case BlockModel::ElementData::Axis::y: scaleFactor.x *= coef; scaleFactor.z *= coef; break; case BlockModel::ElementData::Axis::z: scaleFactor.x *= coef; scaleFactor.y *= coef; break; } rotationMat = glm::scale(rotationMat, scaleFactor); } rotationMat = glm::translate(rotationMat, -rotateOrigin.glm()); elementTransform = rotationMat * elementTransform; } elementTransform = glm::translate(elementTransform, elementOrigin.glm()); elementTransform = glm::scale(elementTransform, elementSize.glm()); for (const auto& face : element.faces) { ParsedFace parsedFace; parsedFace.visibility = face.second.cullface; glm::mat4 faceTransform = glm::mat4(1.0); switch (face.first) { case FaceDirection::down: faceTransform = glm::translate(elementTransform, glm::vec3(0, 0, 0)); faceTransform = glm::rotate(faceTransform, glm::radians(180.0f), glm::vec3(1.0f, 0, 0)); faceTransform = glm::translate(faceTransform, glm::vec3(0, 0, -1)); faceTransform = glm::translate(faceTransform, glm::vec3(0.5f, 0.0f, 0.5f)); faceTransform = glm::rotate(faceTransform, glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f)); faceTransform = glm::translate(faceTransform, glm::vec3(-0.5f, 0.0f, -0.5f)); break; case FaceDirection::up: faceTransform = glm::translate(elementTransform, glm::vec3(0.0f, 1.0f, 0.0f)); faceTransform = glm::translate(faceTransform, glm::vec3(0.5f, 0.0f, 0.5f)); faceTransform = glm::rotate(faceTransform, glm::radians(90.0f), glm::vec3(0.0f, 1.0f, 0.0f)); faceTransform = glm::translate(faceTransform, glm::vec3(-0.5f, 0.0f, -0.5f)); break; case FaceDirection::north: faceTransform = glm::translate(elementTransform, glm::vec3(1, 0, 0)); faceTransform = glm::rotate(faceTransform, glm::radians(90.0f), glm::vec3(-1.0f, 0.0f, 0.0f)); faceTransform = glm::rotate(faceTransform, glm::radians(90.0f), glm::vec3(0.0f, -1.0f, 0.0f)); break; case FaceDirection::south: faceTransform = glm::translate(elementTransform, glm::vec3(0, 0, 1)); faceTransform = glm::rotate(faceTransform, glm::radians(90.0f), glm::vec3(-1.0f, 0.0f, 0.0f)); faceTransform = glm::rotate(faceTransform, glm::radians(90.0f), glm::vec3(0.0f, -1.0f, 0.0f)); faceTransform = glm::translate(faceTransform, glm::vec3(0, 0, -1)); faceTransform = glm::rotate(faceTransform, glm::radians(180.0f), glm::vec3(1, 0, 0.0f)); faceTransform = glm::translate(faceTransform, glm::vec3(0, 0, -1.0f)); break; case FaceDirection::east: faceTransform = glm::translate(elementTransform, glm::vec3(1, 0, 0)); faceTransform = glm::rotate(faceTransform, glm::radians(90.0f), glm::vec3(0, 0.0f, 1.0f)); faceTransform = glm::rotate(faceTransform, glm::radians(180.0f), glm::vec3(1.0f, 0.0f, 0.0f)); faceTransform = glm::translate(faceTransform, glm::vec3(0, 0, -1)); break; case FaceDirection::west: faceTransform = glm::translate(elementTransform, glm::vec3(0, 0, 0)); faceTransform = glm::rotate(faceTransform, glm::radians(90.0f), glm::vec3(0, 0.0f, 1.0f)); break; } parsedFace.transform = faceTransform; TextureCoord texture; unsigned int textureFrames = 1; textureName = face.second.texture; if (model.Textures.empty()) { texture = AssetManager::GetTexture("minecraft/textures/error"); } else { while (textureName[0] == '#') { textureName.erase(0, 1); auto textureIt = model.Textures.find(textureName); textureName = textureIt != model.Textures.end() ? textureIt->second : "minecraft/textures/error"; } textureName.insert(0, "minecraft/textures/"); AssetTexture *assetTexture = AssetManager::GetAsset(textureName); texture = atlas->GetTexture(assetTexture->id); textureFrames = assetTexture->frames; if (!(face.second.uv == BlockModel::ElementData::FaceData::Uv{ 0,16,0,16 }) && !(face.second.uv == BlockModel::ElementData::FaceData::Uv{ 0,0,0,0 }) && !(face.second.uv == BlockModel::ElementData::FaceData::Uv{ 0,0,16,16 })) { double x = face.second.uv.x1; double y = face.second.uv.y1; double w = face.second.uv.x2 - face.second.uv.x1; double h = face.second.uv.y2 - face.second.uv.y1; x /= 16.0; y /= 16.0; w /= 16.0; h /= 16.0; double X = texture.x; double Y = texture.y; double W = texture.w; double H = texture.h; texture.x = X + x * W; texture.y = Y + y * H; texture.w = w * W; texture.h = h * H; } } parsedFace.texture = glm::vec4{ texture.x,texture.y,texture.w,texture.h }; parsedFace.layer = texture.layer; parsedFace.frames = textureFrames; if (face.second.tintIndex) parsedFace.color = glm::vec3(0.486, 0.745, 0.423); else parsedFace.color = glm::vec3(1.0f); model.parsedFaces.push_back(parsedFace); } } }; AssetManager::RecursiveWalkAsset("/minecraft/models/", parseBlockModel); } BlockFaces &AssetManager::GetBlockModelByBlockId(BlockId block) { auto it = blockIdToBlockFaces.find(block); if (it != blockIdToBlockFaces.end()) return it->second; BlockInfo *blockInfo = GetBlockInfo(block); if (blockInfo->blockstate == "@liquid") { BlockFaces blockFaces; blockFaces.isBlock = false; blockFaces.isLiquid = true; blockFaces.transform = glm::mat4(1.0f); blockFaces.ambientOcclusion = false; for (size_t i = 0; i < FaceDirection::none; i++) { blockFaces.faceDirectionVector[i] = FaceDirectionVector[i]; } const auto& liquidInfo = GetBlockLiquidInfo(BlockId{ block.id, 0 }); { AssetTexture* assetTexture = AssetManager::GetAsset("minecraft/textures/" + liquidInfo.flowTexture); if (!assetTexture) return errorFaces; TextureCoord texture = atlas->GetTexture(assetTexture->id); float textureFrames = assetTexture->frames; blockFaces.faces.emplace_back(ParsedFace{ FaceDirection::none, glm::translate(glm::mat4(1.0f), glm::vec3{0.0f, 1.0f, 0.0f}), glm::vec4{ texture.x,texture.y,texture.w,texture.h }, static_cast(texture.layer), static_cast(assetTexture->frames), glm::vec3(1.0f), }); } { AssetTexture* assetTexture = AssetManager::GetAsset("minecraft/textures/" + liquidInfo.stillTexture); if (!assetTexture) return errorFaces; TextureCoord texture = atlas->GetTexture(assetTexture->id); float textureFrames = assetTexture->frames; blockFaces.faces.emplace_back(ParsedFace{ FaceDirection::none, glm::translate(glm::mat4(1.0f), glm::vec3{0.0f, 1.0f, 0.0f}), glm::vec4{ texture.x,texture.y,texture.w,texture.h }, static_cast(texture.layer), static_cast(assetTexture->frames), glm::vec3(1.0f), }); } return blockIdToBlockFaces.try_emplace(block, blockFaces).first->second; } AssetBlockState *asset = GetAsset("/minecraft/blockstates/" + blockInfo->blockstate); if (!asset) return errorFaces; BlockState &blockState = asset->blockState; if (blockState.variants.find(blockInfo->variant) == blockState.variants.end()) return errorFaces; BlockStateVariant &variant = blockState.variants[blockInfo->variant]; if (variant.models.empty()) return errorFaces; BlockStateVariant::Model &model = variant.models[0]; AssetBlockModel *assetModel = GetAsset("/minecraft/models/block/" + model.modelName); if (!assetModel) return errorFaces; BlockFaces blockFaces; blockFaces.transform = glm::mat4(1.0); blockFaces.faces = assetModel->blockModel.parsedFaces; blockFaces.isBlock = assetModel->blockModel.IsBlock; blockFaces.ambientOcclusion = assetModel->blockModel.AmbientOcclusion; blockFaces.isLiquid = false; glm::mat4 transform = glm::mat4(1.0); if (model.y != 0) { blockFaces.transform = glm::translate(blockFaces.transform, glm::vec3(0.5f, 0.0f, 0.5f)); blockFaces.transform = glm::rotate(blockFaces.transform, glm::radians((float)model.y), glm::vec3(0.0f, -1.0f, 0.0f)); blockFaces.transform = glm::translate(blockFaces.transform, glm::vec3(-0.5f, 0.0f, -0.5f)); transform = glm::rotate(transform, glm::radians((float)model.y), glm::vec3(0.0f, -1.0f, 0)); } if (model.x != 0) { blockFaces.transform = glm::translate(blockFaces.transform, glm::vec3(0.0f, 0.5f, 0.5f)); blockFaces.transform = glm::rotate(blockFaces.transform, glm::radians((float)model.x), glm::vec3(1.0f, 0.0f, 0.0f)); blockFaces.transform = glm::translate(blockFaces.transform, glm::vec3(0.0f, -0.5f, -0.5f)); transform = glm::rotate(transform, glm::radians((float)model.x), glm::vec3(1.0f, 0.0f, 0)); } for (int i = 0; i < FaceDirection::none; i++) { glm::vec4 vec = transform * glm::vec4(FaceDirectionVector[i].glm(), 1.0f); blockFaces.faceDirectionVector[i] = Vector(roundf(vec.x), roundf(vec.y), roundf(vec.z)); } return blockIdToBlockFaces.try_emplace(block, blockFaces).first->second; } Asset *AssetManager::GetAssetPtr(const std::string & assetName) { OPTICK_EVENT(); AssetTreeNode *node; if (assetName[0] != '/') node = GetAssetByAssetName('/' + assetName); else node = GetAssetByAssetName(assetName); if (!node) return nullptr; return node->asset.get(); } void AssetManager::RecursiveWalkAsset(const std::string & assetPath, std::function fnc) { AssetTreeNode *assetNode = GetAssetByAssetName(assetPath); std::function walkAssetRecur = [&](AssetTreeNode &node) { fnc(node); for (auto& it : node.childs) { walkAssetRecur(*it.get()); } }; walkAssetRecur(*assetNode); } AssetTreeNode *AssetManager::GetAssetByAssetName(const std::string & assetName) { AssetTreeNode *node = assetTree.get(); unsigned int pos = 1; unsigned int prevPos = 1; size_t x = assetName.size(); while (pos < assetName.size()) { for (; pos < assetName.size() && assetName[pos] != '/'; pos++); std::string dirName = assetName.substr(prevPos, pos - prevPos); for (const auto &asset : node->childs) { if (asset->name == dirName) { node = asset.get(); break; } } pos++; prevPos = pos; } return node; } std::shared_ptr AssetManager::GetTextureAtlas() { return atlas->GetGalTexture(); } TextureCoord AssetManager::GetTexture(const std::string &assetName) { AssetTexture *asset = GetAsset(assetName); if (!asset) return GetTexture("/minecraft/textures/error"); return atlas->GetTexture(asset->id); }