mirror of
https://github.com/PrimeDecomp/retrotool.git
synced 2026-07-12 18:18:58 -07:00
Add LTPB (light probe) viewer
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
use std::io::Cursor;
|
||||
|
||||
use anyhow::{bail, ensure, Result};
|
||||
use binrw::{binrw, BinReaderExt, Endian};
|
||||
|
||||
use crate::format::{
|
||||
chunk::ChunkDescriptor,
|
||||
rfrm::FormDescriptor,
|
||||
txtr::{STextureMetaData, TextureData},
|
||||
CVector3f, CVector3i, FourCC, TaggedVec,
|
||||
};
|
||||
|
||||
// Texture
|
||||
pub const K_FORM_LTPB: FourCC = FourCC(*b"LTPB");
|
||||
|
||||
// Probe header
|
||||
pub const K_CHUNK_PHDR: FourCC = FourCC(*b"PHDR");
|
||||
// Probe texture
|
||||
pub const K_CHUNK_PTEX: FourCC = FourCC(*b"PTEX");
|
||||
|
||||
#[binrw]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct CBakedLightingUniformProbeGridIndex {
|
||||
pub x: u16,
|
||||
pub y: u16,
|
||||
pub z: u16,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LightProbeBundleHeader {
|
||||
pub unk1: u32,
|
||||
pub unk2: u32,
|
||||
pub unk_vec: CVector3f,
|
||||
pub grid_idx1: CBakedLightingUniformProbeGridIndex,
|
||||
pub grid_idx2: CBakedLightingUniformProbeGridIndex,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct LightProbeExtra {
|
||||
pub vec: CVector3i,
|
||||
pub unk: u32,
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Clone, Debug)]
|
||||
struct SLightProbeMetaData {
|
||||
unk1: u32,
|
||||
unk2: u32,
|
||||
#[br(map = |v: TaggedVec<u32, _>| v.data)]
|
||||
meta_offsets: Vec<u64>,
|
||||
#[br(map = |v: TaggedVec<u32, _>| v.data)]
|
||||
txtr_offsets: Vec<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LightProbeData {
|
||||
pub head: LightProbeBundleHeader,
|
||||
pub textures: Vec<TextureData>,
|
||||
pub extra: Vec<LightProbeExtra>,
|
||||
}
|
||||
|
||||
impl LightProbeData {
|
||||
pub fn slice(data: &[u8], meta: &[u8], e: Endian) -> Result<LightProbeData> {
|
||||
let (ltpb_desc, mut ltpb_data, _) = FormDescriptor::slice(data, e)?;
|
||||
ensure!(ltpb_desc.id == K_FORM_LTPB);
|
||||
ensure!(ltpb_desc.reader_version == 66);
|
||||
ensure!(ltpb_desc.writer_version == 73);
|
||||
|
||||
let meta: SLightProbeMetaData = Cursor::new(meta).read_type(e)?;
|
||||
ensure!(meta.meta_offsets.len() == meta.txtr_offsets.len());
|
||||
let texture_count = meta.meta_offsets.len();
|
||||
|
||||
let mut head: Option<LightProbeBundleHeader> = None;
|
||||
while !ltpb_data.is_empty() {
|
||||
let (chunk_desc, chunk_data, remain) = ChunkDescriptor::slice(ltpb_data, e)?;
|
||||
let mut reader = Cursor::new(chunk_data);
|
||||
match chunk_desc.id {
|
||||
K_CHUNK_PHDR => head = Some(reader.read_type(e)?),
|
||||
K_CHUNK_PTEX => {}
|
||||
id => bail!("Unknown LTPB chunk ID {id:?}"),
|
||||
}
|
||||
ltpb_data = remain;
|
||||
}
|
||||
let Some(head) = head else { bail!("Failed to locate PHDR") };
|
||||
|
||||
let mut textures = Vec::with_capacity(texture_count);
|
||||
let mut extra: Vec<LightProbeExtra> = Vec::with_capacity(texture_count);
|
||||
for (meta_offset, txtr_offset) in meta.meta_offsets.into_iter().zip(meta.txtr_offsets) {
|
||||
let meta = &data[meta_offset as usize..];
|
||||
|
||||
// Skip metadata to read extra fields
|
||||
let mut reader = Cursor::new(meta);
|
||||
reader.read_type::<STextureMetaData>(e)?;
|
||||
extra.push(reader.read_type(e)?);
|
||||
|
||||
textures.push(TextureData::slice(&data[txtr_offset as usize..], meta, e)?);
|
||||
}
|
||||
Ok(LightProbeData { head, textures, extra })
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
pub mod chunk;
|
||||
pub mod cmdl;
|
||||
pub mod foot;
|
||||
pub mod ltpb;
|
||||
pub mod mcon;
|
||||
pub mod mtrl;
|
||||
pub mod pack;
|
||||
@@ -178,6 +179,41 @@ impl mint::IntoMint for CColor4f {
|
||||
type MintType = mint::Vector4<f32>;
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct CVector3i {
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
pub z: i32,
|
||||
}
|
||||
|
||||
impl CVector3i {
|
||||
#[inline]
|
||||
pub fn new(x: i32, y: i32, z: i32) -> Self { Self { x, y, z } }
|
||||
|
||||
#[inline]
|
||||
pub fn splat(xyz: i32) -> Self { Self { x: xyz, y: xyz, z: xyz } }
|
||||
|
||||
#[inline]
|
||||
pub fn to_array(self) -> [i32; 3] { [self.x, self.y, self.z] }
|
||||
}
|
||||
impl From<[i32; 3]> for CVector3i {
|
||||
fn from(value: [i32; 3]) -> Self { Self { x: value[0], y: value[1], z: value[2] } }
|
||||
}
|
||||
impl From<CVector3i> for [i32; 3] {
|
||||
fn from(value: CVector3i) -> Self { value.to_array() }
|
||||
}
|
||||
|
||||
impl From<CVector3i> for mint::Vector3<i32> {
|
||||
fn from(value: CVector3i) -> Self { Self::from([value.x, value.y, value.z]) }
|
||||
}
|
||||
impl From<mint::Vector3<i32>> for CVector3i {
|
||||
fn from(value: mint::Vector3<i32>) -> Self { Self { x: value.x, y: value.y, z: value.z } }
|
||||
}
|
||||
impl mint::IntoMint for CVector3i {
|
||||
type MintType = mint::Vector3<i32>;
|
||||
}
|
||||
|
||||
#[binrw]
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct CVector4i {
|
||||
|
||||
@@ -581,9 +581,9 @@ impl TextureData {
|
||||
ensure!(txtr_desc.reader_version == 47);
|
||||
ensure!(txtr_desc.writer_version == 51);
|
||||
|
||||
let (head_desc, head_data, _) = ChunkDescriptor::slice(txtr_data, Endian::Little)?;
|
||||
let (head_desc, head_data, _) = ChunkDescriptor::slice(txtr_data, e)?;
|
||||
ensure!(head_desc.id == K_CHUNK_HEAD);
|
||||
let head: STextureHeader = Cursor::new(head_data).read_type(Endian::Little)?;
|
||||
let head: STextureHeader = Cursor::new(head_data).read_type(e)?;
|
||||
|
||||
// log::debug!("META: {meta:#?}");
|
||||
// log::debug!("HEAD: {head:#?}");
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
use anyhow::Error;
|
||||
use bevy::{
|
||||
asset::{AssetLoader, BoxedFuture, LoadContext, LoadedAsset},
|
||||
prelude::*,
|
||||
render::{renderer::RenderDevice, texture::CompressedImageFormats},
|
||||
};
|
||||
use binrw::Endian;
|
||||
use retrolib::format::{
|
||||
foot::{locate_asset_id, locate_meta},
|
||||
ltpb::{LightProbeBundleHeader, LightProbeData, LightProbeExtra},
|
||||
};
|
||||
|
||||
use crate::loaders::texture::{load_texture_asset, TextureAsset};
|
||||
|
||||
#[derive(Debug, Clone, bevy::reflect::TypeUuid)]
|
||||
#[uuid = "f5d65a8b-ffcc-47ea-8c9d-1ab30cca723c"]
|
||||
pub struct LightProbeAsset {
|
||||
pub head: LightProbeBundleHeader,
|
||||
pub textures: Vec<TextureAsset>,
|
||||
pub extra: Vec<LightProbeExtra>,
|
||||
}
|
||||
|
||||
pub struct LightProbeAssetLoader {
|
||||
supported_formats: CompressedImageFormats,
|
||||
}
|
||||
|
||||
impl FromWorld for LightProbeAssetLoader {
|
||||
fn from_world(world: &mut World) -> Self {
|
||||
let supported_formats = match world.get_resource::<RenderDevice>() {
|
||||
Some(render_device) => CompressedImageFormats::from_features(render_device.features()),
|
||||
None => CompressedImageFormats::all(),
|
||||
};
|
||||
Self { supported_formats }
|
||||
}
|
||||
}
|
||||
|
||||
impl AssetLoader for LightProbeAssetLoader {
|
||||
fn load<'a>(
|
||||
&'a self,
|
||||
bytes: &'a [u8],
|
||||
load_context: &'a mut LoadContext,
|
||||
) -> BoxedFuture<'a, Result<(), Error>> {
|
||||
Box::pin(async move {
|
||||
let id = locate_asset_id(bytes, Endian::Little)?;
|
||||
let meta = locate_meta(bytes, Endian::Little)?;
|
||||
let data = LightProbeData::slice(bytes, meta, Endian::Little)?;
|
||||
info!("Loading light probe {} {:?}", id, data.head);
|
||||
|
||||
let mut textures = Vec::with_capacity(data.textures.len());
|
||||
for texture_data in data.textures {
|
||||
textures.push(load_texture_asset(id, texture_data, &self.supported_formats)?);
|
||||
}
|
||||
load_context.set_default_asset(LoadedAsset::new(LightProbeAsset {
|
||||
head: data.head,
|
||||
textures,
|
||||
extra: data.extra,
|
||||
}));
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn extensions(&self) -> &[&str] { &["ltpb"] }
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod lightprobe;
|
||||
pub mod material;
|
||||
pub mod modcon;
|
||||
pub mod model;
|
||||
@@ -18,11 +19,13 @@ impl Plugin for RetroAssetPlugin {
|
||||
.add_asset::<model::ModelAsset>()
|
||||
.add_asset::<package::PackageDirectory>()
|
||||
.add_asset::<texture::TextureAsset>()
|
||||
.add_asset::<lightprobe::LightProbeAsset>()
|
||||
.init_asset_loader::<material::MaterialAssetLoader>()
|
||||
.init_asset_loader::<modcon::ModConAssetLoader>()
|
||||
.init_asset_loader::<model::ModelAssetLoader>()
|
||||
.init_asset_loader::<package::PackageAssetLoader>()
|
||||
.init_asset_loader::<texture::TextureAssetLoader>()
|
||||
.init_asset_loader::<lightprobe::LightProbeAssetLoader>()
|
||||
.add_system(package::package_loader_system);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use retrolib::format::{
|
||||
decompress_image, slice_texture, ETextureFormat, ETextureType, TextureData, K_FORM_TXTR,
|
||||
},
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::AssetRef;
|
||||
|
||||
@@ -55,58 +56,8 @@ impl AssetLoader for TextureAssetLoader {
|
||||
let data = TextureData::slice(bytes, meta, Endian::Little)?;
|
||||
info!("Loading texture {} {:?}", id, data.head);
|
||||
|
||||
let is_srgb = data.head.format.is_srgb();
|
||||
let slices = slice_texture(&data)?;
|
||||
let (bw, bh, _) = data.head.format.block_size();
|
||||
let format = wgpu_format(data.head.format)
|
||||
.ok_or_else(|| anyhow!("Texture format unsupported: {:?}", data.head.format))?;
|
||||
let supported =
|
||||
texture_format_supported(data.head.kind, format, &self.supported_formats);
|
||||
|
||||
let mut images = Vec::with_capacity(slices.len());
|
||||
for mip in &slices {
|
||||
let mut slice_images = Vec::with_capacity(mip.len());
|
||||
for slice in mip {
|
||||
let slice_data = &data.data[slice.data_range.clone()];
|
||||
slice_images.push(if supported {
|
||||
texture_slice_to_image(
|
||||
format,
|
||||
slice_data.to_vec(),
|
||||
slice.width,
|
||||
slice.height,
|
||||
bw,
|
||||
bh,
|
||||
)
|
||||
} else {
|
||||
Image::from_dynamic(
|
||||
decompress_image(
|
||||
data.head.format,
|
||||
slice.width,
|
||||
slice.height,
|
||||
slice_data,
|
||||
)?,
|
||||
is_srgb,
|
||||
)
|
||||
});
|
||||
}
|
||||
images.push(slice_images);
|
||||
}
|
||||
|
||||
let (image_data, format) = if supported {
|
||||
(data.data.clone(), format)
|
||||
} else {
|
||||
(
|
||||
images.iter().flatten().flat_map(|i| &i.data).cloned().collect(),
|
||||
if is_srgb { TextureFormat::Rgba8UnormSrgb } else { TextureFormat::Rgba8Unorm },
|
||||
)
|
||||
};
|
||||
let texture = texture_to_image(&data, format, image_data)?;
|
||||
load_context.set_default_asset(LoadedAsset::new(TextureAsset {
|
||||
asset_ref: AssetRef { id, kind: K_FORM_TXTR },
|
||||
inner: data,
|
||||
texture,
|
||||
slices: images,
|
||||
}));
|
||||
let asset = load_texture_asset(id, data, &self.supported_formats)?;
|
||||
load_context.set_default_asset(LoadedAsset::new(asset));
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
@@ -114,6 +65,59 @@ impl AssetLoader for TextureAssetLoader {
|
||||
fn extensions(&self) -> &[&str] { &["txtr"] }
|
||||
}
|
||||
|
||||
pub fn load_texture_asset(
|
||||
id: Uuid,
|
||||
data: TextureData,
|
||||
supported_formats: &CompressedImageFormats,
|
||||
) -> Result<TextureAsset> {
|
||||
let is_srgb = data.head.format.is_srgb();
|
||||
let slices = slice_texture(&data)?;
|
||||
let (bw, bh, _) = data.head.format.block_size();
|
||||
let format = wgpu_format(data.head.format)
|
||||
.ok_or_else(|| anyhow!("Texture format unsupported: {:?}", data.head.format))?;
|
||||
let supported = texture_format_supported(data.head.kind, format, supported_formats);
|
||||
|
||||
let mut images = Vec::with_capacity(slices.len());
|
||||
for mip in &slices {
|
||||
let mut slice_images = Vec::with_capacity(mip.len());
|
||||
for slice in mip {
|
||||
let slice_data = &data.data[slice.data_range.clone()];
|
||||
slice_images.push(if supported {
|
||||
texture_slice_to_image(
|
||||
format,
|
||||
slice_data.to_vec(),
|
||||
slice.width,
|
||||
slice.height,
|
||||
bw,
|
||||
bh,
|
||||
)
|
||||
} else {
|
||||
Image::from_dynamic(
|
||||
decompress_image(data.head.format, slice.width, slice.height, slice_data)?,
|
||||
is_srgb,
|
||||
)
|
||||
});
|
||||
}
|
||||
images.push(slice_images);
|
||||
}
|
||||
|
||||
let (image_data, format) = if supported {
|
||||
(data.data.clone(), format)
|
||||
} else {
|
||||
(
|
||||
images.iter().flatten().flat_map(|i| &i.data).cloned().collect(),
|
||||
if is_srgb { TextureFormat::Rgba8UnormSrgb } else { TextureFormat::Rgba8Unorm },
|
||||
)
|
||||
};
|
||||
let texture = texture_to_image(&data, format, image_data)?;
|
||||
Ok(TextureAsset {
|
||||
asset_ref: AssetRef { id, kind: K_FORM_TXTR },
|
||||
inner: data,
|
||||
texture,
|
||||
slices: images,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create an [Image] from a 2D texture slice.
|
||||
fn texture_slice_to_image(
|
||||
format: TextureFormat,
|
||||
|
||||
@@ -223,6 +223,10 @@ fn ui_system(world: &mut World) {
|
||||
load_tab(world, &mut ctx, tab.as_mut());
|
||||
tab_assets.push(tab.asset_ref);
|
||||
}
|
||||
TabType::LightProbe(tab) => {
|
||||
load_tab(world, &mut ctx, tab.as_mut());
|
||||
tab_assets.push(tab.asset_ref);
|
||||
}
|
||||
TabType::Empty => {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
use bevy::{
|
||||
asset::LoadState,
|
||||
ecs::system::{lifetimeless::*, *},
|
||||
prelude::*,
|
||||
};
|
||||
use bevy_egui::{EguiContext, EguiUserTextures};
|
||||
use retrolib::format::txtr::ETextureType;
|
||||
|
||||
use crate::{
|
||||
icon,
|
||||
loaders::lightprobe::LightProbeAsset,
|
||||
tabs::{texture::LoadedTexture, SystemTab, TabState},
|
||||
AssetRef,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct LightProbeTab {
|
||||
pub asset_ref: AssetRef,
|
||||
pub handle: Handle<LightProbeAsset>,
|
||||
pub loaded_textures: Vec<Vec<LoadedTexture>>,
|
||||
}
|
||||
|
||||
impl SystemTab for LightProbeTab {
|
||||
type LoadParam =
|
||||
(SRes<Assets<LightProbeAsset>>, SResMut<Assets<Image>>, SResMut<EguiUserTextures>);
|
||||
type UiParam = (SRes<AssetServer>, SRes<Assets<LightProbeAsset>>);
|
||||
|
||||
fn load(&mut self, _ctx: &mut EguiContext, query: SystemParamItem<'_, '_, Self::LoadParam>) {
|
||||
if !self.loaded_textures.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let (assets, mut images, mut egui_textures) = query;
|
||||
let Some(asset) = assets.get(&self.handle) else { return; };
|
||||
|
||||
self.loaded_textures.reserve_exact(asset.textures.len());
|
||||
for texture in &asset.textures {
|
||||
let mut slices = Vec::with_capacity(texture.slices.len());
|
||||
for mip in &texture.slices {
|
||||
let mut texture_ids = Vec::with_capacity(mip.len());
|
||||
for image in mip {
|
||||
let handle = images.add(image.clone());
|
||||
texture_ids.push(egui_textures.add_image(handle));
|
||||
}
|
||||
let size = mip.first().map(|m| m.texture_descriptor.size).unwrap_or_default();
|
||||
slices.push(LoadedTexture { texture_ids, width: size.width, height: size.height });
|
||||
}
|
||||
self.loaded_textures.push(slices);
|
||||
}
|
||||
}
|
||||
|
||||
fn ui(
|
||||
&mut self,
|
||||
ui: &mut egui::Ui,
|
||||
query: SystemParamItem<'_, '_, Self::UiParam>,
|
||||
_state: &mut TabState,
|
||||
) {
|
||||
let (server, assets) = query;
|
||||
|
||||
ui.label(format!("{} {}", self.asset_ref.kind, self.asset_ref.id));
|
||||
|
||||
match server.get_load_state(&self.handle) {
|
||||
LoadState::NotLoaded => {
|
||||
return;
|
||||
}
|
||||
LoadState::Loading => {
|
||||
ui.spinner();
|
||||
return;
|
||||
}
|
||||
LoadState::Loaded => {}
|
||||
LoadState::Failed => {
|
||||
ui.colored_label(egui::Color32::RED, "Loading failed");
|
||||
return;
|
||||
}
|
||||
LoadState::Unloaded => {
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let Some(asset) = assets.get(&self.handle) else { return; };
|
||||
|
||||
for (txtr_idx, txtr) in asset.textures.iter().enumerate() {
|
||||
ui.group(|ui| {
|
||||
ui.label(format!("Type: {}", txtr.inner.head.kind));
|
||||
ui.label(format!("Format: {}", txtr.inner.head.format));
|
||||
ui.label(format!(
|
||||
"Size: {}x{}x{} (mips: {})",
|
||||
txtr.inner.head.width,
|
||||
txtr.inner.head.height,
|
||||
txtr.inner.head.layers,
|
||||
txtr.inner.head.mip_sizes.len()
|
||||
));
|
||||
|
||||
let mip = &self.loaded_textures[txtr_idx][0];
|
||||
if self.loaded_textures.len() > 1 {
|
||||
ui.label(format!(
|
||||
"Mipmap size: {}x{}x{}",
|
||||
mip.width,
|
||||
mip.height,
|
||||
mip.texture_ids.len(),
|
||||
));
|
||||
}
|
||||
let size = egui::Vec2::new(mip.width as f32, mip.height as f32);
|
||||
let draw_image =
|
||||
|ui: &mut egui::Ui, rect: &egui::Rect, i: usize, x: u32, y: u32, flip: bool| {
|
||||
let min = rect.min + size * egui::Vec2::new(x as f32, y as f32);
|
||||
let y_range = if flip { 1.0..=0.0 } else { 0.0..=1.0 };
|
||||
egui::widgets::Image::new(mip.texture_ids[i], size)
|
||||
.uv(egui::Rect::from_x_y_ranges(0.0..=1.0, y_range))
|
||||
.paint_at(ui, egui::Rect::from_min_size(min, size));
|
||||
};
|
||||
if txtr.inner.head.kind == ETextureType::Cube && mip.texture_ids.len() == 6 {
|
||||
let (_, rect) = ui.allocate_space(size * egui::Vec2::new(4.0, 3.0));
|
||||
draw_image(ui, &rect, 2, 1, 0, false);
|
||||
draw_image(ui, &rect, 1, 0, 1, false);
|
||||
draw_image(ui, &rect, 4, 1, 1, false);
|
||||
draw_image(ui, &rect, 0, 2, 1, false);
|
||||
draw_image(ui, &rect, 5, 3, 1, false);
|
||||
draw_image(ui, &rect, 3, 1, 2, false);
|
||||
} else {
|
||||
let (_, rect) = ui
|
||||
.allocate_space(size * egui::Vec2::new(mip.texture_ids.len() as f32, 1.0));
|
||||
for i in 0..mip.texture_ids.len() {
|
||||
draw_image(ui, &rect, i, i as u32, 0, false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn title(&mut self) -> egui::WidgetText {
|
||||
format!("{} {} {}", icon::LIGHTPROBE_CUBEMAP, self.asset_ref.kind, self.asset_ref.id).into()
|
||||
}
|
||||
|
||||
fn id(&self) -> String { format!("{} {}", self.asset_ref.kind, self.asset_ref.id) }
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod lightprobe;
|
||||
pub mod modcon;
|
||||
pub mod model;
|
||||
pub mod project;
|
||||
@@ -13,6 +14,7 @@ pub enum TabType {
|
||||
Texture(Box<texture::TextureTab>),
|
||||
Model(Box<model::ModelTab>),
|
||||
ModCon(Box<modcon::ModConTab>),
|
||||
LightProbe(Box<lightprobe::LightProbeTab>),
|
||||
Empty,
|
||||
}
|
||||
|
||||
@@ -82,6 +84,7 @@ impl egui_dock::TabViewer for TabViewer<'_> {
|
||||
TabType::Texture(tab) => render_tab(self.world, ui, tab.as_mut(), &mut self.state),
|
||||
TabType::Model(tab) => render_tab(self.world, ui, tab.as_mut(), &mut self.state),
|
||||
TabType::ModCon(tab) => render_tab(self.world, ui, tab.as_mut(), &mut self.state),
|
||||
TabType::LightProbe(tab) => render_tab(self.world, ui, tab.as_mut(), &mut self.state),
|
||||
TabType::Empty => {}
|
||||
}
|
||||
}
|
||||
@@ -92,6 +95,7 @@ impl egui_dock::TabViewer for TabViewer<'_> {
|
||||
TabType::Texture(tab) => tab.title(),
|
||||
TabType::Model(tab) => tab.title(),
|
||||
TabType::ModCon(tab) => tab.title(),
|
||||
TabType::LightProbe(tab) => tab.title(),
|
||||
TabType::Empty => "".into(),
|
||||
}
|
||||
}
|
||||
@@ -111,6 +115,10 @@ impl egui_dock::TabViewer for TabViewer<'_> {
|
||||
close_tab(self.world, tab.as_mut());
|
||||
true
|
||||
}
|
||||
TabType::LightProbe(tab) => {
|
||||
close_tab(self.world, tab.as_mut());
|
||||
true
|
||||
}
|
||||
TabType::Empty => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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},
|
||||
ltpb::K_FORM_LTPB,
|
||||
mcon::K_FORM_MCON,
|
||||
txtr::{ETextureFormat, ETextureType, K_FORM_TXTR},
|
||||
FourCC,
|
||||
@@ -16,7 +17,10 @@ use retrolib::format::{
|
||||
use crate::{
|
||||
icon,
|
||||
loaders::{model::ModelAsset, package::PackageDirectory, texture::TextureAsset},
|
||||
tabs::{modcon::ModConTab, model::ModelTab, texture::TextureTab, SystemTab, TabState, TabType},
|
||||
tabs::{
|
||||
lightprobe::LightProbeTab, modcon::ModConTab, model::ModelTab, texture::TextureTab,
|
||||
SystemTab, TabState, TabType,
|
||||
},
|
||||
AssetRef,
|
||||
};
|
||||
|
||||
@@ -178,6 +182,7 @@ impl SystemTab for ProjectTab {
|
||||
K_FORM_CMDL | K_FORM_SMDL | K_FORM_WMDL => icon::FILE_3D,
|
||||
K_FORM_FMV0 => icon::FILE_MOVIE,
|
||||
K_FORM_ROOM | K_FORM_MCON => icon::SCENE_DATA,
|
||||
K_FORM_LTPB => icon::LIGHTPROBE_GRID,
|
||||
_ => icon::FILE,
|
||||
},
|
||||
entry.kind,
|
||||
@@ -239,6 +244,15 @@ impl SystemTab for ProjectTab {
|
||||
..default()
|
||||
})));
|
||||
}
|
||||
K_FORM_LTPB => {
|
||||
let handle = server.load(format!("{}.{}", entry.id, entry.kind));
|
||||
state.open_tab =
|
||||
Some(TabType::LightProbe(Box::new(LightProbeTab {
|
||||
asset_ref,
|
||||
handle,
|
||||
..default()
|
||||
})));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user