Add model picking in MCON viewer

This commit is contained in:
Luke Street
2023-03-17 01:22:58 -04:00
parent 99183848d0
commit 3fd0ef4a51
7 changed files with 224 additions and 36 deletions
Generated
+66
View File
@@ -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
View File
@@ -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"))
}
+1
View File
@@ -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"
+11 -5
View File
@@ -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(())
})
+4 -1
View File
@@ -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)
+1
View File
@@ -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
+110 -27
View File
@@ -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((