Add MCON viewer (prefabs)

This commit is contained in:
Luke Street
2023-03-16 00:30:49 -04:00
parent a3861f5317
commit 4344a4c87b
13 changed files with 1343 additions and 736 deletions
+84
View File
@@ -0,0 +1,84 @@
use std::io::Cursor;
use anyhow::{ensure, Result};
use binrw::{binrw, BinReaderExt, Endian};
use binrw_derive::binread;
use uuid::Uuid;
use crate::format::{
chunk::ChunkDescriptor, peek_four_cc, rfrm::FormDescriptor, CColor4f, CTransform4f, FourCC,
TaggedVec,
};
// Texture
pub const K_FORM_MCON: FourCC = FourCC(*b"MCON");
const K_CHUNK_MCVD: FourCC = FourCC(*b"MCVD");
#[binrw]
#[derive(Clone, Debug)]
struct SModConHeader {
unk: u32,
}
#[binrw]
#[derive(Clone, Debug)]
pub struct ObjectTransform {
#[br(map = Uuid::from_bytes_le)]
#[bw(map = Uuid::to_bytes_le)]
pub id: Uuid,
pub xf: CTransform4f,
}
#[binread]
#[derive(Clone, Debug)]
pub struct SModConVisualData {
#[br(map = |v: TaggedVec<u32, uuid::Bytes>| v.data.into_iter().map(Uuid::from_bytes_le).collect())]
pub models: Vec<Uuid>,
#[br(map = |v: TaggedVec<u32, uuid::Bytes>| v.data.into_iter().map(Uuid::from_bytes_le).collect())]
pub ids_2: Vec<Uuid>,
#[br(map = |v: TaggedVec<u32, _>| v.data)]
pub colors: Vec<CColor4f>,
#[br(map = |v: TaggedVec<u32, _>| v.data)]
pub transforms: Vec<CTransform4f>,
#[br(map = |v: TaggedVec<u32, _>| v.data)]
pub object_transforms: Vec<ObjectTransform>,
#[br(map = |v: TaggedVec<u32, _>| v.data)]
pub bytes_1: Vec<u8>,
#[br(map = |v: TaggedVec<u32, _>| v.data)]
pub shorts_1: Vec<u16>,
#[br(map = |v: TaggedVec<u32, _>| v.data)]
pub shorts_2: Vec<u16>,
#[br(map = |v: TaggedVec<u32, _>| v.data)]
pub bytes_2: Vec<u8>,
#[br(map = |v: TaggedVec<u32, _>| v.data)]
pub bytes_3: Vec<u8>,
// TODO
}
#[derive(Debug, Clone)]
pub struct ModConData {
pub visual_data: Option<SModConVisualData>,
}
impl ModConData {
pub fn slice(data: &[u8], e: Endian) -> Result<Self> {
let (mcon_desc, mut mcon_data, _) = FormDescriptor::slice(data, Endian::Little)?;
ensure!(mcon_desc.id == K_FORM_MCON);
ensure!(mcon_desc.reader_version == 41);
ensure!(mcon_desc.writer_version == 44);
let mut data = ModConData { visual_data: None };
while !mcon_data.is_empty() {
if peek_four_cc(mcon_data) == *b"PEEK" {
break;
}
let (chunk_desc, chunk_data, remain) = ChunkDescriptor::slice(mcon_data, e)?;
if chunk_desc.id == K_CHUNK_MCVD {
data.visual_data = Some(Cursor::new(chunk_data).read_type(e)?);
}
mcon_data = remain;
}
Ok(data)
}
}
+57 -1
View File
@@ -1,6 +1,7 @@
pub mod chunk;
pub mod cmdl;
pub mod foot;
pub mod mcon;
pub mod mtrl;
pub mod pack;
pub mod rfrm;
@@ -8,10 +9,12 @@ pub mod txtr;
use std::{
fmt::{Debug, Display, Formatter, Write},
marker::PhantomData,
num::TryFromIntError,
string::FromUtf8Error,
};
use binrw::binrw;
use binrw::{binrw, BinRead, BinWrite};
use crate::array_ref;
@@ -85,6 +88,9 @@ impl CColor4f {
impl From<CColor4f> for [f32; 4] {
fn from(value: CColor4f) -> Self { value.to_array() }
}
impl Default for CColor4f {
fn default() -> Self { Self { r: 0.0, g: 0.0, b: 0.0, a: 1.0 } }
}
#[binrw]
#[derive(Copy, Clone, Debug)]
@@ -125,6 +131,19 @@ pub struct CTransform4f {
m23: f32,
}
impl CTransform4f {
#[inline]
#[rustfmt::skip]
pub fn to_matrix_array(&self) -> [f32; 16] {
[
self.m00, self.m01, self.m02, 0.0,
self.m10, self.m11, self.m12, 0.0,
self.m20, self.m21, self.m22, 0.0,
self.m03, self.m13, self.m23, 1.0,
]
}
}
#[binrw]
#[derive(Clone, Debug)]
pub struct COBBox {
@@ -149,3 +168,40 @@ impl CStringFixedName {
fn into_string(self) -> Result<String, FromUtf8Error> { String::from_utf8(self.text) }
}
#[binrw]
#[derive(Clone, Debug, Default)]
struct TaggedVec<C, T>
where
C: for<'a> BinRead<Args<'a> = ()>
+ for<'a> BinWrite<Args<'a> = ()>
+ Copy
+ TryFrom<usize, Error = TryFromIntError>
+ 'static,
T: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()> + 'static,
usize: TryFrom<C, Error = TryFromIntError>,
{
#[bw(try_calc(data.len().try_into()))]
count: C,
#[br(count(count))]
data: Vec<T>,
_marker: PhantomData<C>,
}
impl<C, T> TaggedVec<C, T>
where
C: for<'a> BinRead<Args<'a> = ()>
+ for<'a> BinWrite<Args<'a> = ()>
+ Copy
+ Default
+ TryFrom<usize, Error = TryFromIntError>
+ 'static,
T: for<'a> BinRead<Args<'a> = ()> + for<'a> BinWrite<Args<'a> = ()> + Default + 'static,
usize: TryFrom<C, Error = TryFromIntError>,
{
#[allow(dead_code)]
fn new(inner: Vec<T>) -> Self {
#[allow(clippy::needless_update)]
Self { data: inner, ..Default::default() }
}
}
+1
View File
@@ -1,4 +1,5 @@
pub mod material;
pub mod modcon;
pub mod model;
pub mod package;
pub mod texture;
+57
View File
@@ -0,0 +1,57 @@
use std::path::PathBuf;
use anyhow::Error;
use bevy::{
asset::{AssetLoader, AssetPath, BoxedFuture, LoadContext, LoadedAsset},
prelude::*,
};
use binrw::Endian;
use retrolib::format::mcon::ModConData;
use crate::loaders::model::ModelAsset;
#[derive(Debug, Clone, bevy::reflect::TypeUuid)]
#[uuid = "83269869-1209-408e-8835-bc6f2496e82b"]
pub struct ModConAsset {
pub inner: ModConData,
pub models: Vec<Handle<ModelAsset>>,
}
pub struct ModConAssetLoader;
impl Plugin for ModConAssetLoader {
fn build(&self, app: &mut App) {
app.add_asset::<ModConAsset>().add_asset_loader(ModConAssetLoader);
}
}
impl AssetLoader for ModConAssetLoader {
fn load<'a>(
&'a self,
bytes: &'a [u8],
load_context: &'a mut LoadContext,
) -> BoxedFuture<'a, anyhow::Result<(), Error>> {
Box::pin(async move {
let mcon = ModConData::slice(bytes, Endian::Little)?;
// println!("Loaded MCON: {:?}", mcon);
let mut dependencies = vec![];
let mut models = vec![];
if let Some(visual_data) = &mcon.visual_data {
dependencies.reserve_exact(visual_data.models.len());
models.reserve_exact(visual_data.models.len());
for id in &visual_data.models {
let path = AssetPath::new(PathBuf::from(format!("{id}.CMDL")), None);
dependencies.push(path.clone());
models.push(load_context.get_handle(path));
}
}
load_context.set_default_asset(
LoadedAsset::new(ModConAsset { inner: mcon, models })
.with_dependencies(dependencies),
);
Ok(())
})
}
fn extensions(&self) -> &[&str] { &["mcon"] }
}
+9 -3
View File
@@ -3,7 +3,7 @@ use std::path::PathBuf;
use anyhow::Error;
use bevy::{
app::{App, Plugin},
asset::{AddAsset, AssetLoader, AssetPath, BoxedFuture, LoadContext, LoadedAsset},
asset::{AddAsset, AssetLoader, AssetPath, BoxedFuture, LoadContext, LoadState, LoadedAsset},
prelude::*,
utils::HashMap,
};
@@ -23,6 +23,12 @@ pub struct ModelAsset {
pub textures: HashMap<Uuid, Handle<TextureAsset>>,
}
impl ModelAsset {
pub fn get_load_state(&self, server: &AssetServer) -> LoadState {
server.get_group_load_state(self.textures.values().map(|h| h.id()))
}
}
pub struct ModelAssetLoader;
impl Plugin for ModelAssetLoader {
@@ -40,8 +46,8 @@ impl AssetLoader for ModelAssetLoader {
Box::pin(async move {
let meta = locate_meta(bytes, Endian::Little)?;
let data = ModelData::slice(bytes, meta, Endian::Little)?;
log::info!("Loaded model {:?}", data.head);
log::info!("Loaded meshes {:#?}", data.mesh);
// log::info!("Loaded model {:?}", data.head);
// log::info!("Loaded meshes {:#?}", data.mesh);
let mut dependencies = HashMap::<Uuid, AssetPath>::new();
for mat in &data.mtrl.materials {
for data in &mat.data {
+11 -3
View File
@@ -1,6 +1,7 @@
mod icon;
mod loaders;
mod material;
mod render;
mod tabs;
use std::{path::PathBuf, time::Duration};
@@ -19,6 +20,7 @@ use walkdir::{DirEntry, WalkDir};
use crate::{
loaders::{
material::MaterialAssetLoader,
modcon::ModConAssetLoader,
model::ModelAssetLoader,
package::{
package_loader_system, PackageAssetLoader, PackageDirectory, RetroAssetIoPlugin,
@@ -26,7 +28,8 @@ use crate::{
texture::TextureAssetLoader,
},
material::CustomMaterial,
tabs::{load_tab, model::TemporaryLabel, project::ProjectTab, TabState, TabType, TabViewer},
render::TemporaryLabel,
tabs::{load_tab, project::ProjectTab, TabState, TabType, TabViewer},
};
#[derive(Default, Resource)]
@@ -76,6 +79,7 @@ fn main() {
.add_plugin(TextureAssetLoader)
.add_plugin(ModelAssetLoader)
.add_plugin(MaterialAssetLoader)
.add_plugin(ModConAssetLoader)
.add_plugin(EguiPlugin)
.add_startup_system(setup_icon_font)
.add_system(file_drop)
@@ -179,11 +183,15 @@ fn ui_system(world: &mut World) {
}
TabType::Texture(tab) => {
load_tab(world, &mut ctx, tab);
tab_assets.push(tab.asset_ref.clone());
tab_assets.push(tab.asset_ref);
}
TabType::Model(tab) => {
load_tab(world, &mut ctx, tab);
tab_assets.push(tab.asset_ref.clone());
tab_assets.push(tab.asset_ref);
}
TabType::ModCon(tab) => {
load_tab(world, &mut ctx, tab);
tab_assets.push(tab.asset_ref);
}
TabType::Empty => {}
}
+101
View File
@@ -0,0 +1,101 @@
use bevy::{prelude::*, render::primitives::Aabb};
use egui::PointerButton;
#[derive(Default)]
pub struct ModelCamera {
pub transform: Transform,
pub upside_down: bool,
pub radius: f32,
pub origin: Vec3,
pub projection: Projection,
}
impl ModelCamera {
pub fn init(&mut self, aabb: &Aabb, center: bool) {
let radius = (aabb.max() - aabb.min()).max_element() * 1.25;
if center {
self.origin = aabb.center.into();
}
let mut camera_xf =
Transform::from_xyz(-radius, 5.0, radius).looking_at(self.origin, Vec3::Y);
let rot_matrix = Mat3::from_quat(camera_xf.rotation);
camera_xf.translation = self.origin + rot_matrix.mul_vec3(Vec3::new(0.0, 0.0, radius));
self.transform = camera_xf;
self.radius = radius;
}
pub fn update(
&mut self,
rect: &egui::Rect,
response: &egui::Response,
scroll_delta: egui::Vec2,
) {
let mut any = false;
let mut rotation_move = Vec2::ZERO;
let mut pan = Vec2::ZERO;
let scroll = {
if response.hovered() {
// let delta = ui.input(|i| i.scroll_delta);
Vec2::new(scroll_delta.x, scroll_delta.y)
} else {
Vec2::ZERO
}
};
if response.drag_started_by(PointerButton::Primary)
|| response.drag_released_by(PointerButton::Primary)
{
// only check for upside down when orbiting started or ended this frame
// if the camera is "upside" down, panning horizontally would be inverted, so invert the input to make it correct
let up = self.transform.rotation * Vec3::Y;
self.upside_down = up.y <= 0.0;
}
if response.dragged_by(PointerButton::Primary) {
let delta = response.drag_delta();
rotation_move = Vec2::new(delta.x, delta.y);
} else if response.dragged_by(PointerButton::Middle) {
let delta = response.drag_delta();
pan = Vec2::new(delta.x, delta.y);
}
if rotation_move.length_squared() > 0.0 {
any = true;
let delta_x = {
let delta = rotation_move.x / rect.width() * std::f32::consts::PI * 2.0;
if self.upside_down {
-delta
} else {
delta
}
};
let delta_y = rotation_move.y / rect.height() * std::f32::consts::PI;
let yaw = Quat::from_rotation_y(-delta_x);
let pitch = Quat::from_rotation_x(-delta_y);
self.transform.rotation = yaw * self.transform.rotation; // rotate around global y axis
self.transform.rotation *= pitch; // rotate around local x axis
} else if pan.length_squared() > 0.0 {
any = true;
if let Projection::Perspective(projection) = &self.projection {
pan *= Vec2::new(projection.fov * projection.aspect_ratio, projection.fov)
/ Vec2::new(rect.width(), rect.height());
}
// translate by local axes
let right = self.transform.rotation * Vec3::X * -pan.x;
let up = self.transform.rotation * Vec3::Y * pan.y;
// make panning proportional to distance away from focus point
let translation = (right + up) * self.radius;
self.origin += translation;
} else if scroll.y.abs() > 0.0 {
any = true;
self.radius -= (scroll.y / 50.0/* TODO ? */) * self.radius * 0.2;
// dont allow zoom to reach zero or you get stuck
self.radius = f32::max(self.radius, 0.05);
}
if any {
// emulating parent/child to make the yaw/y-axis rotation behave like a turntable
// parent = x and y rotation
// child = z-offset
let rot_matrix = Mat3::from_quat(self.transform.rotation);
self.transform.translation =
self.origin + rot_matrix.mul_vec3(Vec3::new(0.0, 0.0, self.radius));
}
}
}
+7
View File
@@ -0,0 +1,7 @@
pub mod camera;
pub mod model;
use bevy::prelude::*;
#[derive(Component)]
pub struct TemporaryLabel;
File diff suppressed because it is too large Load Diff
+9 -1
View File
@@ -1,3 +1,4 @@
pub mod modcon;
pub mod model;
pub mod project;
pub mod texture;
@@ -11,6 +12,7 @@ pub enum TabType {
Project(project::ProjectTab),
Texture(texture::TextureTab),
Model(model::ModelTab),
ModCon(modcon::ModConTab),
Empty,
}
@@ -79,6 +81,7 @@ impl egui_dock::TabViewer for TabViewer<'_> {
TabType::Project(tab) => render_tab(self.world, ui, tab, &mut self.state),
TabType::Texture(tab) => render_tab(self.world, ui, tab, &mut self.state),
TabType::Model(tab) => render_tab(self.world, ui, tab, &mut self.state),
TabType::ModCon(tab) => render_tab(self.world, ui, tab, &mut self.state),
TabType::Empty => {}
}
}
@@ -88,6 +91,7 @@ impl egui_dock::TabViewer for TabViewer<'_> {
TabType::Project(tab) => tab.title(),
TabType::Texture(tab) => tab.title(),
TabType::Model(tab) => tab.title(),
TabType::ModCon(tab) => tab.title(),
TabType::Empty => "".into(),
}
}
@@ -103,11 +107,15 @@ impl egui_dock::TabViewer for TabViewer<'_> {
close_tab(self.world, tab);
true
}
TabType::ModCon(tab) => {
close_tab(self.world, tab);
true
}
TabType::Empty => false,
}
}
fn clear_background(&self, tab: &Self::Tab) -> bool {
!matches!(tab, TabType::Empty | TabType::Model(_))
!matches!(tab, TabType::Empty | TabType::Model(_) | TabType::ModCon(_))
}
}
+318
View File
@@ -0,0 +1,318 @@
use bevy::{
asset::LoadState,
core_pipeline::{clear_color::ClearColorConfig, tonemapping::Tonemapping},
ecs::system::{lifetimeless::*, *},
math::Vec3A,
prelude::*,
render::{camera::Viewport, primitives::Aabb, view::RenderLayers},
};
use bevy_egui::EguiContext;
use egui::{Sense, Widget};
use crate::{
icon,
loaders::{modcon::ModConAsset, model::ModelAsset, texture::TextureAsset},
material::CustomMaterial,
render::{
camera::ModelCamera,
model::{convert_transform, load_model},
TemporaryLabel,
},
tabs::{SystemTab, TabState},
AssetRef,
};
pub struct LoadedModel {
pub entity: Entity,
pub visible: bool,
}
pub struct ModelInfo {
pub handle: Handle<ModelAsset>,
pub loaded: Vec<LoadedModel>,
pub transforms: Vec<Transform>,
pub aabb: Aabb,
}
#[derive(Default)]
pub struct ModConTab {
pub asset_ref: AssetRef,
pub handle: Handle<ModConAsset>,
pub models: Vec<ModelInfo>,
pub camera: ModelCamera,
pub diffuse_map: Handle<Image>,
pub specular_map: Handle<Image>,
pub combined_aabb: Aabb,
}
impl ModConTab {
fn get_load_state(
&self,
server: &AssetServer,
assets: &Assets<ModConAsset>,
models: &Assets<ModelAsset>,
) -> LoadState {
match server.get_load_state(&self.handle) {
LoadState::Loaded => {}
state => return state,
};
let asset = match assets.get(&self.handle) {
Some(v) => v,
None => return LoadState::Failed,
};
// Ensure all dependencies loaded
match server.get_group_load_state(asset.models.iter().map(|h| h.id())) {
LoadState::Loaded => {}
state => return state,
}
for model in &asset.models {
let model = models.get(model).unwrap();
match model.get_load_state(server) {
LoadState::Loaded => {}
state => return state,
}
}
LoadState::Loaded
}
}
impl SystemTab for ModConTab {
type LoadParam = (
SCommands,
SResMut<Assets<Mesh>>,
SResMut<Assets<CustomMaterial>>,
SResMut<Assets<ModelAsset>>,
SResMut<Assets<TextureAsset>>,
SResMut<Assets<Image>>,
SResMut<AssetServer>,
SResMut<Assets<ModConAsset>>,
);
type UiParam =
(SCommands, SRes<AssetServer>, SRes<Assets<ModelAsset>>, SRes<Assets<ModConAsset>>);
fn load(&mut self, _ctx: &mut EguiContext, query: SystemParamItem<'_, '_, Self::LoadParam>) {
let (
mut commands,
mut meshes,
mut materials,
mut models,
texture_assets,
mut images,
server,
mod_con_assets,
) = query;
if self.models.is_empty() {
if let Some(mod_con) = mod_con_assets.get(&self.handle) {
let data = match &mod_con.inner.visual_data {
Some(value) => value,
None => return,
};
for handle in &mod_con.models {
self.models.push(ModelInfo {
handle: handle.clone(),
loaded: vec![],
transforms: vec![],
aabb: Default::default(),
});
}
for (idx, &model_idx) in data.shorts_1.iter().enumerate() {
self.models[model_idx as usize]
.transforms
.push(convert_transform(&data.transforms[idx]));
}
self.models.retain(|info| !info.transforms.is_empty());
}
}
let mut loaded = false;
for info in &mut self.models {
if !info.loaded.is_empty() {
for loaded in &info.loaded {
if let Some(mut commands) = commands.get_entity(loaded.entity) {
commands.insert(Visibility::Hidden);
}
}
continue;
}
let asset = match models.get_mut(&info.handle) {
Some(v) => v,
None => continue,
};
// Ensure all dependencies loaded
match asset.get_load_state(&server) {
LoadState::Loaded => println!("Loading model"),
_ => continue,
}
let result = load_model(
asset,
&mut commands,
&texture_assets,
&mut images,
&mut materials,
&mut meshes,
);
let built = match result {
Ok(value) => value,
Err(e) => {
log::error!("Failed to load model: {e:?}");
continue;
}
};
for &transform in &info.transforms {
let entity = commands
.spawn(SpatialBundle { transform, visibility: Visibility::Hidden, ..default() })
.with_children(|builder| {
for idx in built.lod[0].meshes.iter() {
let mesh = &built.meshes[idx];
builder.spawn(MaterialMeshBundle {
mesh: mesh.mesh.clone(),
material: mesh.material.clone(),
..default()
});
}
})
.id();
info.loaded.push(LoadedModel { entity, visible: true });
}
info.aabb = built.aabb;
loaded = true;
}
if loaded {
let all_loaded = self.models.iter().all(|m| !m.loaded.is_empty());
if all_loaded {
let mut min = Vec3A::splat(f32::MAX);
let mut max = Vec3A::splat(f32::MIN);
for info in &self.models {
min = info.aabb.min().min(min);
max = info.aabb.max().max(max);
}
self.camera.init(&Aabb::from_min_max(min.into(), max.into()), true);
}
}
// FIXME
if self.diffuse_map.is_weak() {
self.diffuse_map = server.load("papermill_diffuse_rgb9e5_zstd.ktx2");
self.specular_map = server.load("papermill_specular_rgb9e5_zstd.ktx2");
}
}
fn close(&mut self, query: SystemParamItem<'_, '_, Self::LoadParam>) {
let (mut commands, _, _, _, _, _, _, _) = query;
for model in self.models.iter().flat_map(|l| &l.loaded) {
if let Some(commands) = commands.get_entity(model.entity) {
commands.despawn_recursive();
}
}
}
fn ui(
&mut self,
ui: &mut egui::Ui,
query: SystemParamItem<'_, '_, Self::UiParam>,
state: &mut TabState,
) {
let scale = ui.ctx().pixels_per_point();
let rect = ui.available_rect_before_wrap();
let left_top = rect.left_top().to_vec2() * scale;
let size = rect.size() * scale;
let viewport = Viewport {
physical_position: UVec2 { x: left_top.x as u32, y: left_top.y as u32 },
physical_size: UVec2 { x: size.x as u32, y: size.y as u32 },
depth: 0.0..1.0,
};
let 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 all_loaded = self.models.iter().all(|m| !m.loaded.is_empty());
if !all_loaded {
ui.centered_and_justified(|ui| {
match self.get_load_state(&server, &mod_con_assets, &models) {
LoadState::Failed => egui::Label::new(
egui::RichText::from("Loading failed").heading().color(egui::Color32::RED),
)
.ui(ui),
_ => egui::Spinner::new().size(50.0).ui(ui),
};
});
return;
}
egui::Frame::group(ui.style()).show(ui, |ui| {
egui::ScrollArea::vertical().max_height(rect.height() * 0.25).show(ui, |ui| {
ui.label(format!("Models: {}", self.models.len()));
ui.label(format!(
"Instances: {}",
self.models.iter().map(|m| m.loaded.len()).sum::<usize>()
))
});
});
commands.spawn((
Camera3dBundle {
camera_3d: Camera3d {
clear_color: if state.render_layer == 0 {
ClearColorConfig::Default
} else {
ClearColorConfig::None
},
..default()
},
camera: Camera {
viewport: Some(viewport),
order: state.render_layer as isize,
// hdr: true,
..default()
},
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,
));
// FIXME: https://github.com/bevyengine/bevy/issues/3462
if state.render_layer == 0 {
// commands.spawn((
// DirectionalLightBundle {
// directional_light: DirectionalLight { ..default() },
// transform: Transform::from_xyz(-30.0, 5.0, 20.0)
// .looking_at(Vec3::ZERO, Vec3::Y),
// ..default()
// },
// RenderLayers::layer(state.render_layer),
// TemporaryLabel,
// ));
}
for info in &self.models {
for model in &info.loaded {
if let Some(mut commands) = commands.get_entity(model.entity) {
commands.insert((
if model.visible { Visibility::Visible } else { Visibility::Hidden },
RenderLayers::layer(state.render_layer),
));
}
}
}
state.render_layer += 1;
}
fn title(&mut self) -> egui::WidgetText {
format!("{} {} {}", icon::SCENE_DATA, self.asset_ref.kind, self.asset_ref.id).into()
}
fn id(&self) -> String { format!("{} {}", self.asset_ref.kind, self.asset_ref.id) }
}
File diff suppressed because it is too large Load Diff
+14 -5
View File
@@ -8,6 +8,7 @@ use bevy_egui::{EguiContext, EguiUserTextures};
use egui::{text::LayoutJob, Color32, TextFormat, Widget};
use retrolib::format::{
cmdl::{K_FORM_CMDL, K_FORM_SMDL, K_FORM_WMDL},
mcon::K_FORM_MCON,
txtr::{ETextureFormat, ETextureType, K_FORM_TXTR},
FourCC,
};
@@ -15,7 +16,7 @@ use retrolib::format::{
use crate::{
icon,
loaders::{model::ModelAsset, package::PackageDirectory, texture::TextureAsset},
tabs::{model::ModelTab, texture::TextureTab, SystemTab, TabState, TabType},
tabs::{modcon::ModConTab, model::ModelTab, texture::TextureTab, SystemTab, TabState, TabType},
AssetRef,
};
@@ -172,7 +173,7 @@ impl SystemTab for ProjectTab {
K_FORM_TXTR => icon::TEXTURE,
K_FORM_CMDL | K_FORM_SMDL | K_FORM_WMDL => icon::FILE_3D,
K_FORM_FMV0 => icon::FILE_MOVIE,
K_FORM_ROOM => icon::SCENE_DATA,
K_FORM_ROOM | K_FORM_MCON => icon::SCENE_DATA,
_ => icon::FILE,
},
entry.kind,
@@ -212,7 +213,7 @@ impl SystemTab for ProjectTab {
entry.id, entry.kind
));
state.open_tab = Some(TabType::Texture(TextureTab {
asset_ref: asset_ref.clone(),
asset_ref,
handle,
loaded_texture: None,
}));
@@ -221,9 +222,17 @@ impl SystemTab for ProjectTab {
let handle = server
.load::<ModelAsset, _>(format!("{}.{}", entry.id, entry.kind));
state.open_tab = Some(TabType::Model(ModelTab {
asset_ref: asset_ref.clone(),
asset_ref,
handle,
loaded: None,
..default()
}));
}
K_FORM_MCON => {
let handle = server.load(format!("{}.{}", entry.id, entry.kind));
state.open_tab = Some(TabType::ModCon(ModConTab {
asset_ref,
handle,
..default()
}));
}
_ => {}