mirror of
https://github.com/PrimeDecomp/retrotool.git
synced 2026-07-12 18:18:58 -07:00
Add model picking in MCON viewer
This commit is contained in:
Generated
+66
@@ -606,6 +606,7 @@ dependencies = [
|
||||
"bevy_ptr",
|
||||
"bevy_reflect",
|
||||
"bevy_render",
|
||||
"bevy_sprite",
|
||||
"bevy_tasks",
|
||||
"bevy_time",
|
||||
"bevy_transform",
|
||||
@@ -660,6 +661,14 @@ dependencies = [
|
||||
"glam",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_mod_raycast"
|
||||
version = "0.7.0"
|
||||
source = "git+https://github.com/encounter/bevy_mod_raycast?branch=updates#5769e48316239156e25b674afb3d1e1a7b067523"
|
||||
dependencies = [
|
||||
"bevy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_pbr"
|
||||
version = "0.10.0"
|
||||
@@ -780,6 +789,31 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_sprite"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14aa41c9480b76d7b3c3f1ed89f95c9d6e2a39d3c3367ca82c122d853ac0463e"
|
||||
dependencies = [
|
||||
"bevy_app",
|
||||
"bevy_asset",
|
||||
"bevy_core_pipeline",
|
||||
"bevy_derive",
|
||||
"bevy_ecs",
|
||||
"bevy_log",
|
||||
"bevy_math",
|
||||
"bevy_reflect",
|
||||
"bevy_render",
|
||||
"bevy_transform",
|
||||
"bevy_utils",
|
||||
"bitflags",
|
||||
"bytemuck",
|
||||
"fixedbitset",
|
||||
"guillotiere",
|
||||
"rectangle-pack",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bevy_tasks"
|
||||
version = "0.10.0"
|
||||
@@ -1518,6 +1552,15 @@ dependencies = [
|
||||
"str-buf",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "euclid"
|
||||
version = "0.22.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b52c2ef4a78da0ba68fbe1fd920627411096d2ac478f7f4c9f3a54ba6705bade"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.3"
|
||||
@@ -1787,6 +1830,16 @@ dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "guillotiere"
|
||||
version = "0.6.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b62d5865c036cb1393e23c50693df631d3f5d7bcca4c04fe4cc0fd592e74a782"
|
||||
dependencies = [
|
||||
"euclid",
|
||||
"svg_fmt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.2.1"
|
||||
@@ -2695,6 +2748,12 @@ dependencies = [
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rectangle-pack"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0d463f2884048e7153449a55166f91028d5b0ea53c79377099ce4e8cf0cf9bb"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.16"
|
||||
@@ -2805,6 +2864,7 @@ dependencies = [
|
||||
"bevy",
|
||||
"bevy_egui",
|
||||
"bevy_embedded_assets",
|
||||
"bevy_mod_raycast",
|
||||
"binrw",
|
||||
"bit-set",
|
||||
"bytemuck",
|
||||
@@ -3002,6 +3062,12 @@ version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e08d8363704e6c71fc928674353e6b7c23dcea9d82d7012c8faf2a3a025f8d0"
|
||||
|
||||
[[package]]
|
||||
name = "svg_fmt"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8fb1df15f412ee2e9dfc1c504260fa695c1c3f10fe9f4a6ee2d2184d7d6450e2"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.107"
|
||||
|
||||
+31
-3
@@ -1,7 +1,15 @@
|
||||
use anyhow::{anyhow, ensure, Result};
|
||||
use binrw::Endian;
|
||||
use std::io::Cursor;
|
||||
|
||||
use crate::format::{chunk::ChunkDescriptor, pack::K_CHUNK_META, rfrm::FormDescriptor, FourCC};
|
||||
use anyhow::{anyhow, ensure, Result};
|
||||
use binrw::{BinReaderExt, Endian};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::format::{
|
||||
chunk::ChunkDescriptor,
|
||||
pack::{AssetInfo, K_CHUNK_META},
|
||||
rfrm::FormDescriptor,
|
||||
FourCC,
|
||||
};
|
||||
|
||||
// Custom footer for extracted files
|
||||
pub const K_FORM_FOOT: FourCC = FourCC(*b"FOOT");
|
||||
@@ -28,3 +36,23 @@ pub fn locate_meta(file_data: &[u8], e: Endian) -> Result<&[u8]> {
|
||||
}
|
||||
Err(anyhow!("Failed to locate META chunk"))
|
||||
}
|
||||
|
||||
/// Locate the asset ID in extracted files
|
||||
pub fn locate_asset_id(file_data: &[u8], e: Endian) -> Result<Uuid> {
|
||||
let (_, _, remain) = FormDescriptor::slice(file_data, e)?;
|
||||
let (foot_desc, mut foot_data, remain) = FormDescriptor::slice(remain, Endian::Little)?;
|
||||
ensure!(foot_desc.id == K_FORM_FOOT);
|
||||
ensure!(foot_desc.reader_version == 1);
|
||||
ensure!(foot_desc.writer_version == 1);
|
||||
ensure!(remain.is_empty());
|
||||
|
||||
while !foot_data.is_empty() {
|
||||
let (desc, data, remain) = ChunkDescriptor::slice(foot_data, e)?;
|
||||
if desc.id == K_CHUNK_AINF {
|
||||
let asset_info: AssetInfo = Cursor::new(data).read_type(e)?;
|
||||
return Ok(asset_info.id);
|
||||
}
|
||||
foot_data = remain;
|
||||
}
|
||||
Err(anyhow!("Failed to locate AINF chunk"))
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ anyhow = "1.0.69"
|
||||
astc-decode = "0.3.1"
|
||||
bevy_egui = "0.20.1"
|
||||
bevy_embedded_assets = { version = "0.7.0", optional = true }
|
||||
bevy_mod_raycast = { git = "https://github.com/encounter/bevy_mod_raycast", branch = "updates" }
|
||||
binrw = "0.11.1"
|
||||
bit-set = "0.5.3"
|
||||
bytemuck = "1.13.0"
|
||||
|
||||
@@ -9,16 +9,17 @@ use bevy::{
|
||||
};
|
||||
use binrw::Endian;
|
||||
use retrolib::format::{
|
||||
cmdl::{CMaterialDataInner, ModelData},
|
||||
foot::locate_meta,
|
||||
cmdl::{CMaterialDataInner, ModelData, K_FORM_CMDL},
|
||||
foot::{locate_asset_id, locate_meta},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::loaders::texture::TextureAsset;
|
||||
use crate::{loaders::texture::TextureAsset, AssetRef};
|
||||
|
||||
#[derive(Debug, Clone, bevy::reflect::TypeUuid)]
|
||||
#[uuid = "83269869-1209-408e-8835-bc6f2496e829"]
|
||||
pub struct ModelAsset {
|
||||
pub asset_ref: AssetRef,
|
||||
pub inner: ModelData,
|
||||
pub textures: HashMap<Uuid, Handle<TextureAsset>>,
|
||||
}
|
||||
@@ -44,6 +45,7 @@ impl AssetLoader for ModelAssetLoader {
|
||||
load_context: &'a mut LoadContext,
|
||||
) -> BoxedFuture<'a, anyhow::Result<(), Error>> {
|
||||
Box::pin(async move {
|
||||
let id = locate_asset_id(bytes, Endian::Little)?;
|
||||
let meta = locate_meta(bytes, Endian::Little)?;
|
||||
let data = ModelData::slice(bytes, meta, Endian::Little)?;
|
||||
// log::info!("Loaded model {:?}", data.head);
|
||||
@@ -81,8 +83,12 @@ impl AssetLoader for ModelAssetLoader {
|
||||
.map(|(u, p)| (*u, load_context.get_handle(p.clone())))
|
||||
.collect();
|
||||
load_context.set_default_asset(
|
||||
LoadedAsset::new(ModelAsset { inner: data, textures })
|
||||
.with_dependencies(dependencies.into_values().collect()),
|
||||
LoadedAsset::new(ModelAsset {
|
||||
asset_ref: AssetRef { id, kind: K_FORM_CMDL },
|
||||
inner: data,
|
||||
textures,
|
||||
})
|
||||
.with_dependencies(dependencies.into_values().collect()),
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@@ -12,6 +12,7 @@ use bevy::{
|
||||
window::{PrimaryWindow, WindowResolution},
|
||||
};
|
||||
use bevy_egui::{egui, EguiContext, EguiContexts, EguiPlugin};
|
||||
use bevy_mod_raycast::{DefaultPluginState, DefaultRaycastingPlugin};
|
||||
use egui::{FontFamily, FontId};
|
||||
use retrolib::format::FourCC;
|
||||
use uuid::Uuid;
|
||||
@@ -29,7 +30,7 @@ use crate::{
|
||||
},
|
||||
material::CustomMaterial,
|
||||
render::TemporaryLabel,
|
||||
tabs::{load_tab, project::ProjectTab, TabState, TabType, TabViewer},
|
||||
tabs::{load_tab, modcon::ModConRaycastSet, project::ProjectTab, TabState, TabType, TabViewer},
|
||||
};
|
||||
|
||||
#[derive(Default, Resource)]
|
||||
@@ -74,6 +75,8 @@ fn main() {
|
||||
})
|
||||
.add_before::<AssetPlugin, _>(RetroAssetIoPlugin),
|
||||
)
|
||||
.add_plugin(DefaultRaycastingPlugin::<ModConRaycastSet>::default())
|
||||
.insert_resource(DefaultPluginState::<ModConRaycastSet>::default().with_debug_cursor())
|
||||
.add_plugin(MaterialPlugin::<CustomMaterial>::default())
|
||||
.add_plugin(PackageAssetLoader)
|
||||
.add_plugin(TextureAssetLoader)
|
||||
|
||||
@@ -63,6 +63,7 @@ pub fn load_model(
|
||||
let ModelAsset {
|
||||
inner: ModelData { head, mtrl, mesh, vbuf, ibuf, vtx_buffers, idx_buffers },
|
||||
textures,
|
||||
..
|
||||
} = asset;
|
||||
|
||||
// Build sampler descriptors
|
||||
|
||||
@@ -7,6 +7,7 @@ use bevy::{
|
||||
render::{camera::Viewport, primitives::Aabb, view::RenderLayers},
|
||||
};
|
||||
use bevy_egui::EguiContext;
|
||||
use bevy_mod_raycast::{Intersection, RaycastMesh, RaycastSource};
|
||||
use egui::{Sense, Widget};
|
||||
|
||||
use crate::{
|
||||
@@ -18,7 +19,7 @@ use crate::{
|
||||
model::{convert_transform, load_model},
|
||||
TemporaryLabel,
|
||||
},
|
||||
tabs::{SystemTab, TabState},
|
||||
tabs::{model::ModelTab, SystemTab, TabState, TabType},
|
||||
AssetRef,
|
||||
};
|
||||
|
||||
@@ -34,7 +35,6 @@ pub struct ModelInfo {
|
||||
pub aabb: Aabb,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ModConTab {
|
||||
pub asset_ref: AssetRef,
|
||||
pub handle: Handle<ModConAsset>,
|
||||
@@ -42,6 +42,23 @@ pub struct ModConTab {
|
||||
pub camera: ModelCamera,
|
||||
pub diffuse_map: Handle<Image>,
|
||||
pub specular_map: Handle<Image>,
|
||||
pub env_light: bool,
|
||||
pub selected_model: Option<ModelLabel>,
|
||||
}
|
||||
|
||||
impl Default for ModConTab {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
asset_ref: default(),
|
||||
handle: default(),
|
||||
models: default(),
|
||||
camera: default(),
|
||||
diffuse_map: default(),
|
||||
specular_map: default(),
|
||||
env_light: true,
|
||||
selected_model: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ModConTab {
|
||||
@@ -75,6 +92,13 @@ impl ModConTab {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ModConRaycastSet;
|
||||
|
||||
#[derive(Component, Clone, Debug)]
|
||||
pub struct ModelLabel {
|
||||
pub asset_ref: AssetRef,
|
||||
}
|
||||
|
||||
impl SystemTab for ModConTab {
|
||||
type LoadParam = (
|
||||
SCommands,
|
||||
@@ -86,8 +110,14 @@ impl SystemTab for ModConTab {
|
||||
SResMut<AssetServer>,
|
||||
SResMut<Assets<ModConAsset>>,
|
||||
);
|
||||
type UiParam =
|
||||
(SCommands, SRes<AssetServer>, SRes<Assets<ModelAsset>>, SRes<Assets<ModConAsset>>);
|
||||
type UiParam = (
|
||||
SCommands,
|
||||
SRes<AssetServer>,
|
||||
SRes<Assets<ModelAsset>>,
|
||||
SRes<Assets<ModConAsset>>,
|
||||
SQuery<&'static Parent, With<Intersection<ModConRaycastSet>>>,
|
||||
SQuery<&'static ModelLabel>,
|
||||
);
|
||||
|
||||
fn load(&mut self, _ctx: &mut EguiContext, query: SystemParamItem<'_, '_, Self::LoadParam>) {
|
||||
let (
|
||||
@@ -165,19 +195,25 @@ impl SystemTab for ModConTab {
|
||||
^ transform.scale.y.is_sign_negative()
|
||||
^ transform.scale.z.is_sign_negative();
|
||||
let entity = commands
|
||||
.spawn(SpatialBundle { transform, visibility: Visibility::Hidden, ..default() })
|
||||
.spawn((
|
||||
SpatialBundle { transform, visibility: Visibility::Hidden, ..default() },
|
||||
ModelLabel { asset_ref: asset.asset_ref },
|
||||
))
|
||||
.with_children(|builder| {
|
||||
for idx in built.lod[0].meshes.iter() {
|
||||
let mesh = &built.meshes[idx];
|
||||
builder.spawn(MaterialMeshBundle {
|
||||
mesh: mesh.mesh.clone(),
|
||||
material: if is_mirrored {
|
||||
mesh.mirrored_material.clone()
|
||||
} else {
|
||||
mesh.material.clone()
|
||||
builder.spawn((
|
||||
MaterialMeshBundle {
|
||||
mesh: mesh.mesh.clone(),
|
||||
material: if is_mirrored {
|
||||
mesh.mirrored_material.clone()
|
||||
} else {
|
||||
mesh.material.clone()
|
||||
},
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
});
|
||||
RaycastMesh::<ModConRaycastSet>::default(),
|
||||
));
|
||||
}
|
||||
})
|
||||
.id();
|
||||
@@ -228,11 +264,11 @@ impl SystemTab for ModConTab {
|
||||
physical_size: UVec2 { x: size.x as u32, y: size.y as u32 },
|
||||
depth: 0.0..1.0,
|
||||
};
|
||||
let response =
|
||||
let mut response =
|
||||
ui.interact(rect, ui.make_persistent_id("background"), Sense::click_and_drag());
|
||||
self.camera.update(&rect, &response, ui.input(|i| i.scroll_delta));
|
||||
|
||||
let (mut commands, server, models, mod_con_assets) = query;
|
||||
let (mut commands, server, models, mod_con_assets, intersection_query, model_query) = query;
|
||||
if !self.models.iter().all(|m| !m.loaded.is_empty()) {
|
||||
ui.centered_and_justified(|ui| {
|
||||
match self.get_load_state(&server, &mod_con_assets, &models) {
|
||||
@@ -246,17 +282,54 @@ impl SystemTab for ModConTab {
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(parent) = intersection_query.iter().next() {
|
||||
self.selected_model = Some(model_query.get(parent.get()).unwrap().clone());
|
||||
}
|
||||
egui::Frame::group(ui.style()).show(ui, |ui| {
|
||||
egui::ScrollArea::vertical().max_height(rect.height() * 0.25).show(ui, |ui| {
|
||||
ui.checkbox(&mut self.env_light, "Environment lighting");
|
||||
ui.label(format!("Models: {}", self.models.len()));
|
||||
ui.label(format!(
|
||||
"Instances: {}",
|
||||
self.models.iter().map(|m| m.loaded.len()).sum::<usize>()
|
||||
))
|
||||
));
|
||||
if let Some(selected) = &self.selected_model {
|
||||
ui.label(format!("Hovering: {}", selected.asset_ref.id));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
commands.spawn((
|
||||
if let Some(selected) = &self.selected_model {
|
||||
let mut shown = false;
|
||||
response = response.context_menu(|ui| {
|
||||
if ui.button("Open in new tab").clicked() {
|
||||
let handle = server
|
||||
.load(format!("{}.{}", selected.asset_ref.id, selected.asset_ref.kind));
|
||||
state.open_tab = Some(TabType::Model(ModelTab {
|
||||
asset_ref: selected.asset_ref,
|
||||
handle,
|
||||
..default()
|
||||
}));
|
||||
ui.close_menu();
|
||||
}
|
||||
if ui.button("Copy GUID").clicked() {
|
||||
ui.output_mut(|out| out.copied_text = format!("{}", selected.asset_ref.id));
|
||||
ui.close_menu();
|
||||
}
|
||||
shown = true;
|
||||
});
|
||||
if !shown {
|
||||
self.selected_model = None;
|
||||
}
|
||||
}
|
||||
|
||||
let camera = Camera {
|
||||
viewport: Some(viewport),
|
||||
order: state.render_layer as isize,
|
||||
// hdr: true,
|
||||
..default()
|
||||
};
|
||||
let mut entity = commands.spawn((
|
||||
Camera3dBundle {
|
||||
camera_3d: Camera3d {
|
||||
clear_color: if state.render_layer == 0 {
|
||||
@@ -266,24 +339,34 @@ impl SystemTab for ModConTab {
|
||||
},
|
||||
..default()
|
||||
},
|
||||
camera: Camera {
|
||||
viewport: Some(viewport),
|
||||
order: state.render_layer as isize,
|
||||
// hdr: true,
|
||||
..default()
|
||||
},
|
||||
camera: camera.clone(),
|
||||
tonemapping: Tonemapping::TonyMcMapface,
|
||||
transform: self.camera.transform,
|
||||
..default()
|
||||
},
|
||||
// BloomSettings::default(),
|
||||
EnvironmentMapLight {
|
||||
diffuse_map: self.diffuse_map.clone(),
|
||||
specular_map: self.specular_map.clone(),
|
||||
},
|
||||
RenderLayers::layer(state.render_layer),
|
||||
TemporaryLabel,
|
||||
));
|
||||
if self.env_light {
|
||||
entity.insert(EnvironmentMapLight {
|
||||
diffuse_map: self.diffuse_map.clone(),
|
||||
specular_map: self.specular_map.clone(),
|
||||
});
|
||||
}
|
||||
if response.hovered() {
|
||||
if let Some(pos) = ui.input(|i| {
|
||||
i.pointer.hover_pos().map(|pos| {
|
||||
Vec2::new(pos.x, i.screen_rect.height() - pos.y) * i.pixels_per_point
|
||||
})
|
||||
}) {
|
||||
entity.insert(RaycastSource::<ModConRaycastSet>::new_screenspace(
|
||||
pos,
|
||||
&camera,
|
||||
&GlobalTransform::default(),
|
||||
));
|
||||
}
|
||||
}
|
||||
// FIXME: https://github.com/bevyengine/bevy/issues/3462
|
||||
if state.render_layer == 0 {
|
||||
// commands.spawn((
|
||||
|
||||
Reference in New Issue
Block a user