diff --git a/docs/asset_reference.md b/docs/asset_reference.md index 06af0ef6..13aa6e89 100644 --- a/docs/asset_reference.md +++ b/docs/asset_reference.md @@ -1,6 +1,6 @@ # Asset Reference -This file was generated from asset_schema.wtf and is for version 29 of the asset format. +This file was generated from asset_schema.wtf and is for version 30 of the asset format. ## Index @@ -1111,14 +1111,13 @@ No children. | - | - | - | - | - | | name | The name of the material being referenced. | String | Yes | RAC/GC/UYA/DL | | wrap_mode | The UV wrapping mode, stored as an array of two strings. Possible values are ["repeat" "repeat"] (the default), ["repeat" "clamp"], ["clamp" "repeat"] and ["clamp" "clamp"]. | Array | No | RAC/GC/UYA/DL | -| glass | Only for mobies. Make the material shiny, like glass? | Boolean | *Not yet documented.* | *Not yet documented.* | -| chrome | Only for mobies. Make the material shiny, like chrome? | Boolean | *Not yet documented.* | *Not yet documented.* | +| effect_mode | List of effect modifiers, only relevant for mobies. Possible values are [], ["none"], ["chrome"] and ["glass"]. | Array | No | RAC/GC/UYA/DL | *Children* | Name | Description | Allowed Types | Required | Games | | - | - | - | - | - | -| diffuse | The diffuse map (main texture). | Texture | Yes | RAC/GC/UYA/DL | +| diffuse | The diffuse map (main texture). | Texture | No | RAC/GC/UYA/DL | ### Collision diff --git a/docs/asset_system.md b/docs/asset_system.md index 3ac251ac..df4e1e7f 100644 --- a/docs/asset_system.md +++ b/docs/asset_system.md @@ -144,6 +144,7 @@ Each asset type is defined in `asset_schema.wtf` and a code generator, `asset_co | Format Version | Wrench Version | Description | | - | - | - | +| 30 | | Added effect_mode attribute to Material assets, removed chrome and glass attributes, and made diffuse children optional. | | 29 | | Added hero_groups attributes to Collision assets. | | 28 | | Use glTF (.glb) for the moby models instead of COLLADA. | | 27 | v0.5 | Use glTF (.glb) for the shrub and sky models instead of COLLADA. The mesh attribute of the SkyShell asset is now of type Mesh instead of Collection. The starting_rotation and angular_velocity attributes now only will only apply for UYA and DL (which is more correct). | diff --git a/docs/instance_reference.md b/docs/instance_reference.md index 0f31658a..2867ff59 100644 --- a/docs/instance_reference.md +++ b/docs/instance_reference.md @@ -1,6 +1,6 @@ # Instance Reference -This file was generated from instance_schema.wtf and is for version 29 of the instance format. +This file was generated from instance_schema.wtf and is for version 30 of the instance format. ## Instances diff --git a/src/assetmgr/asset_schema.wtf b/src/assetmgr/asset_schema.wtf index dfcc4576..c7520569 100644 --- a/src/assetmgr/asset_schema.wtf +++ b/src/assetmgr/asset_schema.wtf @@ -16,7 +16,7 @@ along with this program. If not, see . */ -format_version: 29 +format_version: 30 // ***************************************************************************** @@ -1714,19 +1714,18 @@ AssetType Material { games: [1 2 3 4] StringAttribute element {} } - - BooleanAttribute glass { - desc: "Only for mobies. Make the material shiny, like glass?" - } - - BooleanAttribute chrome { - desc: "Only for mobies. Make the material shiny, like chrome?" + + ArrayAttribute effect_mode { + desc: "List of effect modifiers, only relevant for mobies. Possible values are [], [\"none\"], [\"chrome\"] and [\"glass\"]." + required: false + games: [1 2 3 4] + StringAttribute element {} } Child diffuse { desc: "The diffuse map (main texture)." allowed_types: ["Texture"] - required: true + required: false games: [1 2 3 4] } } diff --git a/src/assetmgr/material_asset.cpp b/src/assetmgr/material_asset.cpp index 31fe0d64..90b15e9f 100644 --- a/src/assetmgr/material_asset.cpp +++ b/src/assetmgr/material_asset.cpp @@ -27,17 +27,21 @@ MaterialSet read_material_assets(const CollectionAsset& src) src.for_each_logical_child_of_type([&](const MaterialAsset& asset) { Material& material = materials.emplace_back(); material.name = asset.name(); - const TextureAsset& diffuse = asset.get_diffuse(); - std::string diffuse_path = asset.get_diffuse().src().path.string(); - auto key = std::make_pair(&diffuse.file(), diffuse_path); - auto iter = texture_indices.find(key); - if (iter == texture_indices.end()) { - texture_indices[key] = next_texture_index; - material.surface = MaterialSurface(next_texture_index); - next_texture_index++; - textures.emplace_back(diffuse.file(), asset.get_diffuse().src().path); + if (asset.has_diffuse()) { + const TextureAsset& diffuse = asset.get_diffuse(); + std::string diffuse_path = asset.get_diffuse().src().path.string(); + auto key = std::make_pair(&diffuse.file(), diffuse_path); + auto iter = texture_indices.find(key); + if (iter == texture_indices.end()) { + texture_indices[key] = next_texture_index; + material.surface = MaterialSurface(next_texture_index); + next_texture_index++; + textures.emplace_back(diffuse.file(), asset.get_diffuse().src().path); + } else { + material.surface = MaterialSurface(iter->second); + } } else { - material.surface = MaterialSurface(iter->second); + material.surface = MaterialSurface(glm::vec4(1.f, 0.f, 1.f, 1.f)); } if (asset.has_wrap_mode()) { std::vector wrap_mode = asset.wrap_mode(); @@ -50,6 +54,19 @@ MaterialSet read_material_assets(const CollectionAsset& src) } } } + if (asset.has_effect_mode()) { + for(std::string effect : asset.effect_mode()) { + if (effect == "none") { + material.effect_mode = MaterialEffectMode::NONE; + } + if (effect == "chrome") { + material.effect_mode = MaterialEffectMode::CHROME; + } + if (effect == "glass") { + material.effect_mode = MaterialEffectMode::GLASS; + } + } + } }); return {materials, textures}; } diff --git a/src/core/gltf.cpp b/src/core/gltf.cpp index 785524a0..111dd72f 100644 --- a/src/core/gltf.cpp +++ b/src/core/gltf.cpp @@ -116,6 +116,7 @@ struct GLTFBuffer // GLTF, Scenes & Nodes static ModelFile read_gltf(const Json& src, Buffer bin_chunk); static Json write_gltf(const ModelFile& src, OutBuffer bin_chunk); +static void validate_gltf(const ModelFile& gltf); static Asset read_asset(const Json& src); static Json write_asset(const Asset& src); static Scene read_scene(const Json& src); @@ -256,6 +257,7 @@ ModelFile read_glb(Buffer src) try { auto json = Json::parse(src.lo + json_offset, src.lo + json_offset + json_size); gltf = read_gltf(json, src.subbuf(bin_offset, bin_size)); + validate_gltf(gltf); } catch (Json::exception& e) { verify_not_reached("%s", e.what()); } @@ -265,6 +267,8 @@ ModelFile read_glb(Buffer src) std::vector write_glb(const ModelFile& gltf) { + validate_gltf(gltf); + std::vector result; OutBuffer dest(result); @@ -590,6 +594,71 @@ static Json write_gltf(const ModelFile& src, OutBuffer bin_chunk) return dest; } +static void validate_gltf(const ModelFile& gltf) +{ + for (const Scene& scene : gltf.scenes) { + const char* name = scene.name.has_value() ? scene.name->c_str() : "unnamed"; + + for (NodeIndex node : scene.nodes) { + verify(node >= 0 && node < gltf.nodes.size(), + "Out of bounds node index %d for scene '%s'.", node, name); + } + } + + for (const Node& node : gltf.nodes) { + const char* name = node.name.has_value() ? node.name->c_str() : "unnamed"; + + for (NodeIndex child : node.children) { + verify(child >= 0 && child < gltf.nodes.size(), + "Out of bounds child index %d for node '%s'.", child, name); + } + + if (node.mesh.has_value()) { + verify(*node.mesh >= 0 && *node.mesh < gltf.meshes.size(), + "Out of bounds mesh index %d for node '%s'.", *node.mesh, name); + } + } + + for (const Animation& animation : gltf.animations) { + const char* name = animation.name.has_value() ? animation.name->c_str() : "unnamed"; + + for (SamplerIndex sampler : animation.sampler_input) { + verify(sampler >= 0 && sampler < gltf.samplers.size(), + "Out of bounds sampler index %d for animation '%s'.", sampler, name); + } + } + + for (const Mesh& mesh : gltf.meshes) { + const char* name = mesh.name.has_value() ? mesh.name->c_str() : "unnamed"; + + for (const MeshPrimitive& primitive : mesh.primitives) { + if (primitive.material.has_value()) { + verify(*primitive.material >= 0 && *primitive.material < gltf.materials.size(), + "Out of bounds material index %d for mesh '%s'.", *primitive.material, name); + } + } + } + + for (const Material& material : gltf.materials) { + const char* name = material.name.has_value() ? material.name->c_str() : "unnamed"; + + if (material.pbr_metallic_roughness.has_value() && material.pbr_metallic_roughness->base_color_texture.has_value()) { + const TextureInfo& info = *material.pbr_metallic_roughness->base_color_texture; + verify(info.index >= 0 && info.index < gltf.textures.size(), + "Out of bounds texture index %d for material '%s'.", info.index, name); + } + } + + for (const Texture& texture : gltf.textures) { + const char* name = texture.name.has_value() ? texture.name->c_str() : "unnamed"; + + if (texture.sampler.has_value()) { + verify(*texture.sampler >= 0 && *texture.sampler < gltf.samplers.size(), + "Out of bounds sampler index %d for texture '%s'.", *texture.sampler, name); + } + } +} + static Asset read_asset(const Json& src) { Asset dest; diff --git a/src/core/gltf.h b/src/core/gltf.h index 62199213..c6f67844 100644 --- a/src/core/gltf.h +++ b/src/core/gltf.h @@ -25,6 +25,16 @@ namespace GLTF { +using SceneIndex = s32; +using NodeIndex = s32; +using AnimationIndex = s32; +using MeshIndex = s32; +using MaterialIndex = s32; +using TextureIndex = s32; +using ImageIndex = s32; +using SamplerIndex = s32; +using SkinIndex = s32; + struct Asset { Opt copyright; @@ -35,17 +45,17 @@ struct Asset struct Scene { - std::vector nodes; + std::vector nodes; Opt name; }; struct Node { // unimplemented: camera - std::vector children; - Opt skin; + std::vector children; + Opt skin; Opt matrix; - Opt mesh; + Opt mesh; Opt rotation; Opt scale; Opt translation; @@ -70,12 +80,12 @@ struct Animation { Opt name; std::vector channel_groups; - std::vector sampler_input; + std::vector sampler_input; }; struct TextureInfo { - s32 index; + TextureIndex index; Opt tex_coord; }; @@ -133,7 +143,7 @@ struct MeshPrimitive { u32 attributes_bitfield = 0; std::vector indices; - Opt material; + Opt material; Opt mode; // unimplemented: targets }; @@ -148,7 +158,7 @@ struct Mesh struct Texture { - Opt sampler; + Opt sampler; Opt source; Opt name; }; diff --git a/src/core/material.cpp b/src/core/material.cpp index 820c29c5..a43083e4 100644 --- a/src/core/material.cpp +++ b/src/core/material.cpp @@ -36,8 +36,8 @@ EffectiveMaterialsOutput effective_materials(const std::vector& materi equal &= materials[i].wrap_mode_s == materials[j].wrap_mode_s; equal &= materials[i].wrap_mode_t == materials[j].wrap_mode_t; } - if (attributes & MATERIAL_ATTRIB_METAL_MODE) { - equal &= materials[i].metal_mode == materials[j].metal_mode; + if (attributes & MATERIAL_ATTRIB_EFFECT_MODE) { + equal &= materials[i].effect_mode == materials[j].effect_mode; } if (equal) { effective.materials.emplace_back((s32) j); diff --git a/src/core/material.h b/src/core/material.h index 747b9a30..15b0ea5c 100644 --- a/src/core/material.h +++ b/src/core/material.h @@ -55,9 +55,9 @@ enum class WrapMode REPEAT, CLAMP }; -enum class MetalEffectMode +enum class MaterialEffectMode { - OFF, CHROME, GLASS + OFF, NONE, CHROME, GLASS }; struct Material @@ -66,14 +66,14 @@ struct Material MaterialSurface surface; WrapMode wrap_mode_s = WrapMode::REPEAT; WrapMode wrap_mode_t = WrapMode::REPEAT; - MetalEffectMode metal_mode = MetalEffectMode::OFF; + MaterialEffectMode effect_mode = MaterialEffectMode::OFF; }; enum MaterialAttribute { MATERIAL_ATTRIB_SURFACE = 1 << 1, MATERIAL_ATTRIB_WRAP_MODE = 1 << 2, - MATERIAL_ATTRIB_METAL_MODE = 1 << 3 + MATERIAL_ATTRIB_EFFECT_MODE = 1 << 3 }; // An effective material is a set of materials that for some subset of diff --git a/src/instancemgr/instance_schema.wtf b/src/instancemgr/instance_schema.wtf index 76c395cd..f4dfd062 100644 --- a/src/instancemgr/instance_schema.wtf +++ b/src/instancemgr/instance_schema.wtf @@ -16,7 +16,7 @@ along with this program. If not, see . */ -format_version: 29 +format_version: 30 InstanceType Moby { desc: "Moving, interactive, or otherwise dynamic objects with an associated update function." @@ -24,7 +24,7 @@ InstanceType Moby { transform_mode: "POSITION_ROTATION_SCALE" variable: "moby_instances" link_type: "mobylink" - + s8 mission {} s32 uid {} s32 bolts {} diff --git a/src/wrenchbuild/classes/moby_class.cpp b/src/wrenchbuild/classes/moby_class.cpp index 798fa767..31655526 100644 --- a/src/wrenchbuild/classes/moby_class.cpp +++ b/src/wrenchbuild/classes/moby_class.cpp @@ -39,8 +39,9 @@ static void unpack_moby_mesh( bool animated, const std::string name); static void pack_mesh_only_class(OutputStream& dest, const MobyClassAsset& src, BuildConfig config); -static s32 count_materials(const CollectionAsset& materials); static void unpack_materials(CollectionAsset& materials, GLTF::ModelFile& gltf); +static void handle_special_materials(CollectionAsset& materials, GLTF::ModelFile& gltf); +static void handle_invalid_materials(CollectionAsset& materials, GLTF::ModelFile& gltf); static bool test_moby_class_core( std::vector& src, AssetType type, BuildConfig config, const char* hint, AssetTestMode mode); @@ -118,11 +119,6 @@ static void unpack_phat_class(MobyClassAsset& dest, InputStream& src, BuildConfi { unpack_asset_impl(dest.core(), src, nullptr, config); - s32 texture_count = 0; - if (!g_asset_unpacker.dump_binaries && dest.has_materials()) { - texture_count = count_materials(dest.get_materials()); - } - std::vector buffer = src.read_multiple(0, src.size()); MOBY::MobyClassData data = MOBY::read_class(buffer, config.game()); @@ -138,9 +134,7 @@ static void unpack_phat_class(MobyClassAsset& dest, InputStream& src, BuildConfi unpack_moby_mesh(gltf, *scene, data.bangles[i].low_lod, data.scale, animated, stringf("bangle_%zu_low_lod", i)); } - if (!g_asset_unpacker.dump_binaries && dest.has_materials()) { - unpack_materials(dest.get_materials(), gltf); - } + unpack_materials(dest.materials(), gltf); std::vector glb = GLTF::write_glb(gltf); auto [stream, ref] = dest.file().open_binary_file_for_writing("mesh.glb"); @@ -164,25 +158,11 @@ static void unpack_mesh_only_class( unpack_moby_mesh(gltf, *scene, meshes.high_lod, scale, animated, "moby"); unpack_moby_mesh(gltf, *scene, meshes.low_lod, scale, animated, "moby_low_lod"); - if (!g_asset_unpacker.dump_binaries && dest.has_materials()) { - unpack_materials(dest.get_materials(), gltf); - } + unpack_materials(dest.materials(), gltf); std::vector glb = GLTF::write_glb(gltf); auto [stream, ref] = dest.file().open_binary_file_for_writing("mesh.glb"); stream->write_v(glb); - - //MobyClassCoreAsset& core = dest.core(); - // - //MeshAsset& moby_mesh = core.mesh(); - //moby_mesh.set_name("moby"); - //moby_mesh.set_src(ref); - // - //MeshAsset& moby_low_lod_mesh = core.low_lod_mesh(); - //moby_low_lod_mesh.set_name("moby_low_lod"); - //moby_low_lod_mesh.set_src(ref); - // - //core.set_scale(scale); } static void unpack_moby_mesh( @@ -232,19 +212,6 @@ static void pack_mesh_only_class(OutputStream& dest, const MobyClassAsset& src, dest.write_v(buffer); } -static s32 count_materials(const CollectionAsset& materials) -{ - s32 texture_count = 0; - for (s32 i = 0; i < 16; i++) { - if (materials.has_child(i)) { - texture_count++; - } else { - break; - } - } - return texture_count; -} - static void unpack_materials(CollectionAsset& materials, GLTF::ModelFile& gltf) { for (s32 i = 0; i < 16; i++) { @@ -270,6 +237,63 @@ static void unpack_materials(CollectionAsset& materials, GLTF::ModelFile& gltf) material_asset.set_name(*material.name); } + + handle_special_materials(materials, gltf); + handle_invalid_materials(materials, gltf); +} + +static void handle_special_materials(CollectionAsset& materials, GLTF::ModelFile& gltf) +{ + std::optional special_material_indices[3]; + static const char* special_material_names[3] = { "none", "chrome", "glass" }; + static const glm::vec4 special_material_colours[3] = + { glm::vec4(1.f, 0.f, 1.f, 1.f), glm::vec4(0.5f, 0.5f, 0.5f, 1.f), glm::vec4(1.f, 1.f, 1.f, 1.f) }; + + for (GLTF::Mesh& mesh : gltf.meshes) { + for (GLTF::MeshPrimitive& primitive : mesh.primitives) { + if (primitive.material.has_value() && *primitive.material >= -3 && *primitive.material <= -1) { + s32 index = -*primitive.material - 1; + + std::optional& material_index = special_material_indices[index]; + if (!material_index.has_value()) { + MaterialAsset& material_asset = materials.child(special_material_names[index]); + material_asset.set_name(special_material_names[index]); + + material_index = gltf.materials.size(); + GLTF::Material& material = gltf.materials.emplace_back(); + material.name = special_material_names[index]; + GLTF::MaterialPbrMetallicRoughness& pbr = material.pbr_metallic_roughness.emplace(); + pbr.base_color_factor = special_material_colours[index]; + } + + primitive.material = material_index; + } + } + } +} + +static void handle_invalid_materials(CollectionAsset& materials, GLTF::ModelFile& gltf) +{ + static const char* error_material_name = "error"; + + Opt placeholder_material_index; + for (GLTF::Mesh& mesh : gltf.meshes) { + for (GLTF::MeshPrimitive& primitive : mesh.primitives) { + if (primitive.material.has_value() && (*primitive.material < 0 || *primitive.material >= gltf.materials.size())) { + if (!placeholder_material_index.has_value()) { + MaterialAsset& material_asset = materials.child(error_material_name); + material_asset.set_name(error_material_name); + + placeholder_material_index = (GLTF::MaterialIndex) gltf.materials.size(); + GLTF::Material& placeholder_material = gltf.materials.emplace_back(); + placeholder_material.name = error_material_name; + GLTF::MaterialPbrMetallicRoughness& pbr = placeholder_material.pbr_metallic_roughness.emplace(); + pbr.base_color_factor = glm::vec4(1.f, 0.f, 1.f, 1.f); + } + primitive.material = placeholder_material_index; + } + } + } } static bool test_moby_class_core( diff --git a/src/wrenchbuild/level/collision_asset.cpp b/src/wrenchbuild/level/collision_asset.cpp index 61e24106..4db76cbc 100644 --- a/src/wrenchbuild/level/collision_asset.cpp +++ b/src/wrenchbuild/level/collision_asset.cpp @@ -64,10 +64,6 @@ static void unpack_collision_asset(CollisionAsset& dest, InputStream& src, Build static void pack_collision_asset(OutputStream& dest, const CollisionAsset& src, BuildConfig config) { - if (g_asset_packer_dry_run) { - return; - } - pack_level_collision(dest, src, nullptr, nullptr, -1); } @@ -84,6 +80,10 @@ void pack_level_collision( const Gameplay* gameplay, s32 chunk) { + if (g_asset_packer_dry_run) { + return; + } + ColladaScene scene; for (s32 i = 0; i < 256; i++) { diff --git a/src/wrenchbuild/level/level_classes.h b/src/wrenchbuild/level/level_classes.h index a8331b33..c1b6e044 100644 --- a/src/wrenchbuild/level/level_classes.h +++ b/src/wrenchbuild/level/level_classes.h @@ -37,6 +37,7 @@ void unpack_moby_classes( BuildConfig config, s32 moby_stash_addr, const std::set& moby_stash); +void unpack_rac1_armor_classes(); void pack_moby_classes( OutputStream& index, OutputStream& core, diff --git a/src/wrenchbuild/level/level_core.cpp b/src/wrenchbuild/level/level_core.cpp index f9fcca07..d555e234 100644 --- a/src/wrenchbuild/level/level_core.cpp +++ b/src/wrenchbuild/level/level_core.cpp @@ -165,14 +165,36 @@ void unpack_level_core( } } } - + + // TODO: Unpack to main moby_classes directory, and refactor this into a + // separate function? if (config.game() == Game::RAC) { + auto moby_class_entries = index.read_multiple(header.moby_classes); + auto moby_class_textures = index.read_multiple(header.moby_textures); + CollectionAsset& gadgets = dest.gadgets(SWITCH_FILES); auto gadget_entries = index.read_multiple(header.gadget_offset_rac1, header.gadget_count_rac1); + for (RacGadgetHeader& entry : gadget_entries) { ByteRange range{entry.offset_in_asset_wad, (s32) data.size() - entry.offset_in_asset_wad}; MobyClassAsset& moby = gadgets.foreign_child(entry.class_number); moby.set_id(entry.class_number); + + const MobyClassEntry* class_entry = nullptr; + for (const MobyClassEntry& test_class_entry : moby_class_entries) { + if (test_class_entry.o_class == entry.class_number) { + class_entry = &test_class_entry; + } + } + verify(class_entry, "No moby class entry for gadget %d.", entry.class_number); + + bool stashed = moby_stash.contains(entry.class_number); + if (stashed) { + moby.set_stash_textures(true); + } + + unpack_level_materials(moby.materials(), class_entry->textures, moby_class_textures, texture_data, gs_ram, config.game(), stashed ? moby_stash_addr : -1); + unpack_compressed_asset(moby, data, range, config, FMT_MOBY_CLASS_PHAT); } } @@ -251,6 +273,7 @@ void pack_level_core( std::vector gs_table; std::vector part_defs; if (!g_asset_packer_dry_run) { + // TODO: Pack R&C1 gadget textures. shared = read_level_textures(first_chunk_asset.get_tfrags().get_materials(), mobies, ties, shrubs); for (LevelTexture& record : shared.textures) { diff --git a/src/wrenchbuild/level/sky_asset.cpp b/src/wrenchbuild/level/sky_asset.cpp index aa26597b..cb40d2b4 100644 --- a/src/wrenchbuild/level/sky_asset.cpp +++ b/src/wrenchbuild/level/sky_asset.cpp @@ -26,8 +26,6 @@ #include #include -#define SEPERATE_SKY_CLUSTERS - static void unpack_sky_asset(SkyAsset& dest, InputStream& src, BuildConfig config); static void pack_sky_asset(OutputStream& dest, const SkyAsset& src, BuildConfig config); static void unpack_sky_textures(