diff --git a/Cargo.lock b/Cargo.lock index 7e97207..cd9e5d0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3055,6 +3055,7 @@ dependencies = [ "binrw", "binrw_derive", "ddsfile", + "flate2", "log", "memmap2", "tegra_swizzle", @@ -3090,10 +3091,13 @@ dependencies = [ "bevy", "bevy_egui", "binrw", + "bytemuck", "egui", "egui_dock", + "half", "image", "log", + "num-traits", "retrolib", "uuid", "walkdir", diff --git a/lib/Cargo.toml b/lib/Cargo.toml index e5c61e4..fbf0399 100644 --- a/lib/Cargo.toml +++ b/lib/Cargo.toml @@ -14,6 +14,7 @@ anyhow = "1.0.69" binrw = "0.11.1" binrw_derive = "0.11.1" ddsfile = { git = "https://github.com/encounter/ddsfile", rev = "880f04c1dffa680eab0e9e09cfa58591fe186a31" } +flate2 = "1.0.25" log = "0.4.17" memmap2 = "0.5.9" tegra_swizzle = "0.3.0" diff --git a/lib/src/format/cmdl.rs b/lib/src/format/cmdl.rs index 736e189..966ded3 100644 --- a/lib/src/format/cmdl.rs +++ b/lib/src/format/cmdl.rs @@ -1,4 +1,4 @@ -use std::{borrow::Cow, io::Cursor}; +use std::io::Cursor; use anyhow::{bail, ensure, Result}; use binrw::{binrw, BinReaderExt, Endian}; @@ -745,11 +745,11 @@ pub enum EMaterialFlag { MFC4 = 26, } -fn decompress_gpu_buffers<'a>( - file_data: &'a [u8], +fn decompress_gpu_buffers( + file_data: &[u8], read_info: &[SModelReadBufferInfo], buffer_info: &[SModelBufferInfo], -) -> Result>> { +) -> Result>> { let mut out = Vec::with_capacity(buffer_info.len()); for info in buffer_info { let read_info = &read_info[info.read_index as usize]; @@ -757,23 +757,24 @@ fn decompress_gpu_buffers<'a>( &file_data[read_info.offset as usize..(read_info.offset + read_info.size) as usize]; let comp_buf = &read_buffer[info.offset as usize..(info.offset + info.size) as usize]; let (_, buf) = decompress_buffer(comp_buf, info.dest_size as u64)?; - out.push(buf); + out.push(buf.into_owned()); } Ok(out) } -pub struct ModelData<'a> { +#[derive(Debug, Clone)] +pub struct ModelData { pub head: SModelHeader, pub mtrl: SMaterialChunk, pub mesh: SMeshLoadInformation, pub vbuf: SVertexBufferInfoSection, pub ibuf: SIndexBufferInfoSection, - pub vtx_buffers: Vec>, - pub idx_buffers: Vec>, + pub vtx_buffers: Vec>, + pub idx_buffers: Vec>, } -impl ModelData<'_> { - pub fn slice<'a>(data: &'a [u8], meta: &[u8], e: Endian) -> Result> { +impl ModelData { + pub fn slice(data: &[u8], meta: &[u8], e: Endian) -> Result { let (cmdl_desc, mut cmdl_data, _) = FormDescriptor::slice(data, Endian::Little)?; ensure!(cmdl_desc.id == K_FORM_CMDL); ensure!(cmdl_desc.version_a == 114); diff --git a/lib/src/format/mod.rs b/lib/src/format/mod.rs index 663aab5..ec1eca6 100644 --- a/lib/src/format/mod.rs +++ b/lib/src/format/mod.rs @@ -1,6 +1,7 @@ pub mod chunk; pub mod cmdl; pub mod foot; +pub mod mtrl; pub mod pack; pub mod rfrm; pub mod txtr; diff --git a/lib/src/format/mtrl.rs b/lib/src/format/mtrl.rs new file mode 100644 index 0000000..b9cacf9 --- /dev/null +++ b/lib/src/format/mtrl.rs @@ -0,0 +1,43 @@ +use std::io::{Cursor, Read}; + +use anyhow::{ensure, Result}; +use binrw::{binrw, BinReaderExt, Endian}; +use flate2::bufread::ZlibDecoder; + +use crate::format::{rfrm::FormDescriptor, FourCC}; + +// Texture +pub const K_FORM_MTRL: FourCC = FourCC(*b"MTRL"); + +#[binrw] +#[derive(Clone, Debug)] +struct SMaterialMetaData { + unk1: u32, // count? + unk2: u32, // reader version? + compressed_size: u32, + decompressed_size: u32, + file_offset: u32, +} + +#[derive(Debug, Clone)] +pub struct MaterialData { + pub decompressed: Vec, +} + +impl MaterialData { + pub fn slice(data: &[u8], meta: &[u8], e: Endian) -> Result { + let (mtrl_desc, _, _) = FormDescriptor::slice(data, Endian::Little)?; + ensure!(mtrl_desc.id == K_FORM_MTRL); + ensure!(mtrl_desc.version_a == 168); + ensure!(mtrl_desc.version_b == 168); + + let meta: SMaterialMetaData = Cursor::new(meta).read_type(e)?; + let mut reader = ZlibDecoder::new( + &data[meta.file_offset as usize..(meta.file_offset + meta.compressed_size) as usize], + ); + let mut decompressed = vec![0u8; meta.decompressed_size as usize]; + reader.read_exact(&mut decompressed)?; + + Ok(MaterialData { decompressed }) + } +} diff --git a/retrotool-gui/Cargo.toml b/retrotool-gui/Cargo.toml index 959a3c3..22b1748 100644 --- a/retrotool-gui/Cargo.toml +++ b/retrotool-gui/Cargo.toml @@ -19,10 +19,13 @@ astc-decode = "0.3.1" bevy = "0.9.1" bevy_egui = { git = "https://github.com/mvlabat/bevy_egui", rev = "65f37508339e3a1abe54ae901dad7f49e820cd41" } binrw = "0.11.1" +bytemuck = "1.13.0" egui = "0.21.0" egui_dock = "0.4.0" +half = "2.2.1" image = "0.24.5" log = "0.4.17" +num-traits = "0.2.15" retrolib = { path = "../lib" } uuid = "1.3.0" walkdir = "2.3.2" diff --git a/retrotool-gui/icon.ttf b/retrotool-gui/icon.ttf new file mode 100644 index 0000000..a688d27 Binary files /dev/null and b/retrotool-gui/icon.ttf differ diff --git a/retrotool-gui/src/icon.rs b/retrotool-gui/src/icon.rs new file mode 100644 index 0000000..b29405d --- /dev/null +++ b/retrotool-gui/src/icon.rs @@ -0,0 +1,668 @@ +#![allow(dead_code)] + +pub const ACTION: char = '\u{e900}'; +pub const ACTION_TWEAK: char = '\u{e901}'; +pub const ADD: char = '\u{e902}'; +pub const ALIASED: char = '\u{e903}'; +pub const ALIGN_BOTTOM: char = '\u{e904}'; +pub const ALIGN_CENTER: char = '\u{e905}'; +pub const ALIGN_FLUSH: char = '\u{e906}'; +pub const ALIGN_JUSTIFY: char = '\u{e907}'; +pub const ALIGN_LEFT: char = '\u{e908}'; +pub const ALIGN_MIDDLE: char = '\u{e909}'; +pub const ALIGN_RIGHT: char = '\u{e90a}'; +pub const ALIGN_TOP: char = '\u{e90b}'; +pub const ANCHOR_BOTTOM: char = '\u{e90c}'; +pub const ANCHOR_CENTER: char = '\u{e90d}'; +pub const ANCHOR_LEFT: char = '\u{e90e}'; +pub const ANCHOR_RIGHT: char = '\u{e90f}'; +pub const ANCHOR_TOP: char = '\u{e910}'; +pub const ANIM: char = '\u{e911}'; +pub const ANIM_DATA: char = '\u{e912}'; +pub const ANTIALIASED: char = '\u{e913}'; +pub const APPEND_BLEND: char = '\u{e914}'; +pub const ARMATURE_DATA: char = '\u{e915}'; +pub const ARROW_LEFTRIGHT: char = '\u{e916}'; +pub const ASSET_MANAGER: char = '\u{e917}'; +pub const AUTO: char = '\u{e918}'; +pub const AUTOMERGE_OFF: char = '\u{e919}'; +pub const AUTOMERGE_ON: char = '\u{e91a}'; +pub const AXIS_FRONT: char = '\u{e91b}'; +pub const AXIS_SIDE: char = '\u{e91c}'; +pub const AXIS_TOP: char = '\u{e91d}'; +pub const BACK: char = '\u{e91e}'; +pub const BLENDER: char = '\u{e91f}'; +pub const BOIDS: char = '\u{e920}'; +pub const BOLD: char = '\u{e921}'; +pub const BONE_DATA: char = '\u{e922}'; +pub const BOOKMARKS: char = '\u{e923}'; +pub const BORDERMOVE: char = '\u{e924}'; +pub const BRUSH_DATA: char = '\u{e925}'; +pub const BRUSHES_ALL: char = '\u{e926}'; +pub const CAMERA_DATA: char = '\u{e927}'; +pub const CAMERA_STEREO: char = '\u{e928}'; +pub const CANCEL: char = '\u{e929}'; +pub const CENTER_ONLY: char = '\u{e92a}'; +pub const CHECKBOX_DEHLT: char = '\u{e92b}'; +pub const CHECKBOX_HLT: char = '\u{e92c}'; +pub const CHECKMARK: char = '\u{e92d}'; +pub const CLIP_UV_DEHLT: char = '\u{e92e}'; +pub const CLIP_UV_HLT: char = '\u{e92f}'; +pub const COLLAPSEMENU: char = '\u{e930}'; +pub const COLLECTION_NEW: char = '\u{e931}'; +pub const COLOR: char = '\u{e932}'; +pub const COLOR_ALPHA: char = '\u{e933}'; +pub const COLOR_BLUE: char = '\u{e934}'; +pub const COLOR_GREEN: char = '\u{e935}'; +pub const COLOR_RED: char = '\u{e936}'; +pub const COMMUNITY: char = '\u{e937}'; +pub const CON_ACTION: char = '\u{e938}'; +pub const CON_ARMATURE: char = '\u{e939}'; +pub const CON_CAMERASOLVER: char = '\u{e93a}'; +pub const CON_CHILDOF: char = '\u{e93b}'; +pub const CON_CLAMPTO: char = '\u{e93c}'; +pub const CON_DISTLIMIT: char = '\u{e93d}'; +pub const CONE: char = '\u{e93e}'; +pub const CON_FLOOR: char = '\u{e93f}'; +pub const CON_FOLLOWPATH: char = '\u{e940}'; +pub const CON_FOLLOWTRACK: char = '\u{e941}'; +pub const CON_KINEMATIC: char = '\u{e942}'; +pub const CON_LOCKTRACK: char = '\u{e943}'; +pub const CON_LOCLIKE: char = '\u{e944}'; +pub const CON_LOCLIMIT: char = '\u{e945}'; +pub const CON_OBJECTSOLVER: char = '\u{e946}'; +pub const CON_PIVOT: char = '\u{e947}'; +pub const CON_ROTLIKE: char = '\u{e948}'; +pub const CON_ROTLIMIT: char = '\u{e949}'; +pub const CON_SAMEVOL: char = '\u{e94a}'; +pub const CON_SHRINKWRAP: char = '\u{e94b}'; +pub const CON_SIZELIKE: char = '\u{e94c}'; +pub const CON_SIZELIMIT: char = '\u{e94d}'; +pub const CONSOLE: char = '\u{e94e}'; +pub const CON_SPLINEIK: char = '\u{e94f}'; +pub const CONSTRAINT: char = '\u{e950}'; +pub const CONSTRAINT_BONE: char = '\u{e951}'; +pub const CON_STRETCHTO: char = '\u{e952}'; +pub const CON_TRACKTO: char = '\u{e953}'; +pub const CON_TRANSFORM: char = '\u{e954}'; +pub const CON_TRANSFORM_CACHE: char = '\u{e955}'; +pub const CON_TRANSLIKE: char = '\u{e956}'; +pub const COPY_DOWN: char = '\u{e957}'; +pub const COPY_ID: char = '\u{e958}'; +pub const CUBE: char = '\u{e959}'; +pub const CURSOR: char = '\u{e95a}'; +pub const CURVE_BEZCIRCLE: char = '\u{e95b}'; +pub const CURVE_BEZCURVE: char = '\u{e95c}'; +pub const CURVE_DATA: char = '\u{e95d}'; +pub const CURVE_NCIRCLE: char = '\u{e95e}'; +pub const CURVE_NCURVE: char = '\u{e95f}'; +pub const CURVE_PATH: char = '\u{e960}'; +pub const DECORATE: char = '\u{e961}'; +pub const DECORATE_ANIMATE: char = '\u{e962}'; +pub const DECORATE_DRIVER: char = '\u{e963}'; +pub const DECORATE_KEYFRAME: char = '\u{e964}'; +pub const DECORATE_LIBRARY_OVERRIDE: char = '\u{e965}'; +pub const DECORATE_LINKED: char = '\u{e966}'; +pub const DECORATE_LOCKED: char = '\u{e967}'; +pub const DECORATE_OVERRIDE: char = '\u{e968}'; +pub const DECORATE_UNLOCKED: char = '\u{e969}'; +pub const DESKTOP: char = '\u{e96a}'; +pub const DISCLOSURE_TRI_DOWN: char = '\u{e96b}'; +pub const DISCLOSURE_TRI_RIGHT: char = '\u{e96c}'; +pub const DISK: char = '\u{e96d}'; +pub const DISK_DRIVE: char = '\u{e96e}'; +pub const DOCUMENTS: char = '\u{e96f}'; +pub const DOT: char = '\u{e970}'; +pub const DOWNARROW_HLT: char = '\u{e971}'; +pub const DRIVER: char = '\u{e972}'; +pub const DRIVER_DISTANCE: char = '\u{e973}'; +pub const DRIVER_ROTATIONAL_DIFFERENCE: char = '\u{e974}'; +pub const DRIVER_TRANSFORM: char = '\u{e975}'; +pub const DUPLICATE: char = '\u{e976}'; +pub const EDGE_SELECT: char = '\u{e977}'; +pub const EDITMODE_HLT: char = '\u{e978}'; +pub const EMPTY_ARROWS: char = '\u{e979}'; +pub const EMPTY_AXIS: char = '\u{e97a}'; +pub const EMPTY_DATA: char = '\u{e97b}'; +pub const EMPTY_SINGLE_ARROW: char = '\u{e97c}'; +pub const ERROR: char = '\u{e97d}'; +pub const EXPERIMENTAL: char = '\u{e97e}'; +pub const EXTERNAL_DRIVE: char = '\u{e97f}'; +pub const EYEDROPPER: char = '\u{e980}'; +pub const FACE_MAPS: char = '\u{e981}'; +pub const FACE_SELECT: char = '\u{e982}'; +pub const FAKE_USER_OFF: char = '\u{e983}'; +pub const FAKE_USER_ON: char = '\u{e984}'; +pub const FCURVE: char = '\u{e985}'; +pub const FCURVE_SNAPSHOT: char = '\u{e986}'; +pub const FF: char = '\u{e987}'; +pub const FILE: char = '\u{e988}'; +pub const FILE_3D: char = '\u{e989}'; +pub const FILE_ARCHIVE: char = '\u{e98a}'; +pub const FILE_BACKUP: char = '\u{e98b}'; +pub const FILE_BLANK: char = '\u{e98c}'; +pub const FILE_BLEND: char = '\u{e98d}'; +pub const FILEBROWSER: char = '\u{e98e}'; +pub const FILE_CACHE: char = '\u{e98f}'; +pub const FILE_FOLDER: char = '\u{e990}'; +pub const FILE_FONT: char = '\u{e991}'; +pub const FILE_HIDDEN: char = '\u{e992}'; +pub const FILE_IMAGE: char = '\u{e993}'; +pub const FILE_MOVIE: char = '\u{e994}'; +pub const FILE_NEW: char = '\u{e995}'; +pub const FILE_PARENT: char = '\u{e996}'; +pub const FILE_REFRESH: char = '\u{e997}'; +pub const FILE_SCRIPT: char = '\u{e998}'; +pub const FILE_SOUND: char = '\u{e999}'; +pub const FILE_TEXT: char = '\u{e99a}'; +pub const FILE_TICK: char = '\u{e99b}'; +pub const FILE_VOLUME: char = '\u{e99c}'; +pub const FILTER: char = '\u{e99d}'; +pub const FOLDER_REDIRECT: char = '\u{e99e}'; +pub const FONT_DATA: char = '\u{e99f}'; +pub const FONT_PREVIEW: char = '\u{e9a0}'; +pub const FORCE_BOID: char = '\u{e9a1}'; +pub const FORCE_CHARGE: char = '\u{e9a2}'; +pub const FORCE_CURVE: char = '\u{e9a3}'; +pub const FORCE_DRAG: char = '\u{e9a4}'; +pub const FORCE_FLUIDFLOW: char = '\u{e9a5}'; +pub const FORCE_FORCE: char = '\u{e9a6}'; +pub const FORCE_HARMONIC: char = '\u{e9a7}'; +pub const FORCE_LENNARDJONES: char = '\u{e9a8}'; +pub const FORCE_MAGNETIC: char = '\u{e9a9}'; +pub const FORCE_TEXTURE: char = '\u{e9aa}'; +pub const FORCE_TURBULENCE: char = '\u{e9ab}'; +pub const FORCE_VORTEX: char = '\u{e9ac}'; +pub const FORCE_WIND: char = '\u{e9ad}'; +pub const FORWARD: char = '\u{e9ae}'; +pub const FRAME_NEXT: char = '\u{e9af}'; +pub const FRAME_PREV: char = '\u{e9b0}'; +pub const FREEZE: char = '\u{e9b1}'; +pub const FULLSCREEN_ENTER: char = '\u{e9b2}'; +pub const FULLSCREEN_EXIT: char = '\u{e9b3}'; +pub const FUND: char = '\u{e9b4}'; +pub const GHOST_DISABLED: char = '\u{e9b5}'; +pub const GHOST_ENABLED: char = '\u{e9b6}'; +pub const GIZMO: char = '\u{e9b7}'; +pub const GP_MULTIFRAME_EDITING: char = '\u{e9b8}'; +pub const GP_ONLY_SELECTED: char = '\u{e9b9}'; +pub const GP_SELECT_BETWEEN_STROKES: char = '\u{e9ba}'; +pub const GP_SELECT_POINTS: char = '\u{e9bb}'; +pub const GP_SELECT_STROKES: char = '\u{e9bc}'; +pub const GRAPH: char = '\u{e9bd}'; +pub const GREASEPENCIL: char = '\u{e9be}'; +pub const GRID: char = '\u{e9bf}'; +pub const GRIP: char = '\u{e9c0}'; +pub const GROUP: char = '\u{e9c1}'; +pub const GROUP_BONE: char = '\u{e9c2}'; +pub const GROUP_UVS: char = '\u{e9c3}'; +pub const GROUP_VCOL: char = '\u{e9c4}'; +pub const GROUP_VERTEX: char = '\u{e9c5}'; +pub const HAIR: char = '\u{e9c6}'; +pub const HAIR_DATA: char = '\u{e9c7}'; +pub const HAND: char = '\u{e9c8}'; +pub const HANDLE_ALIGNED: char = '\u{e9c9}'; +pub const HANDLE_AUTO: char = '\u{e9ca}'; +pub const HANDLE_AUTOCLAMPED: char = '\u{e9cb}'; +pub const HANDLE_FREE: char = '\u{e9cc}'; +pub const HANDLE_VECTOR: char = '\u{e9cd}'; +pub const HEART: char = '\u{e9ce}'; +pub const HELP: char = '\u{e9cf}'; +pub const HIDE_OFF: char = '\u{e9d0}'; +pub const HIDE_ON: char = '\u{e9d1}'; +pub const HOLDOUT_OFF: char = '\u{e9d2}'; +pub const HOLDOUT_ON: char = '\u{e9d3}'; +pub const HOME: char = '\u{e9d4}'; +pub const HOOK: char = '\u{e9d5}'; +pub const IMAGE: char = '\u{e9d6}'; +pub const IMAGE_ALPHA: char = '\u{e9d7}'; +pub const IMAGE_BACKGROUND: char = '\u{e9d8}'; +pub const IMAGE_DATA: char = '\u{e9d9}'; +pub const IMAGE_PLANE: char = '\u{e9da}'; +pub const IMAGE_REFERENCE: char = '\u{e9db}'; +pub const IMAGE_RGB: char = '\u{e9dc}'; +pub const IMAGE_RGB_ALPHA: char = '\u{e9dd}'; +pub const IMAGE_ZDEPTH: char = '\u{e9de}'; +pub const IMGDISPLAY: char = '\u{e9df}'; +pub const IMPORT: char = '\u{e9e0}'; +pub const INDIRECT_ONLY_OFF: char = '\u{e9e1}'; +pub const INDIRECT_ONLY_ON: char = '\u{e9e2}'; +pub const INFO: char = '\u{e9e3}'; +pub const INTERPOLATE_BEZIER: char = '\u{e9e4}'; +pub const INTERPOLATE_CIRCLE: char = '\u{e9e5}'; +pub const INTERPOLATE_CONSTANT: char = '\u{e9e6}'; +pub const INTERPOLATE_CUBIC: char = '\u{e9e7}'; +pub const INTERPOLATE_EXPONENTIAL: char = '\u{e9e8}'; +pub const INTERPOLATE_LINEAR: char = '\u{e9e9}'; +pub const INTERPOLATE_QUAD: char = '\u{e9ea}'; +pub const INTERPOLATE_QUARTIC: char = '\u{e9eb}'; +pub const INTERPOLATE_QUINTIC: char = '\u{e9ec}'; +pub const INTERPOLATE_SINE: char = '\u{e9ed}'; +pub const INVERSE_SQUARE_CURVE: char = '\u{e9ee}'; +pub const IPO_BACK: char = '\u{e9ef}'; +pub const IPO_BOUNCE: char = '\u{e9f0}'; +pub const IPO_EASE_IN: char = '\u{e9f1}'; +pub const IPO_EASE_IN_OUT: char = '\u{e9f2}'; +pub const IPO_EASE_OUT: char = '\u{e9f3}'; +pub const IPO_ELASTIC: char = '\u{e9f4}'; +pub const ITALIC: char = '\u{e9f5}'; +pub const KEY_DEHLT: char = '\u{e9f6}'; +pub const KEYFRAME: char = '\u{e9f7}'; +pub const KEYFRAME_HLT: char = '\u{e9f8}'; +pub const KEY_HLT: char = '\u{e9f9}'; +pub const KEYINGSET: char = '\u{e9fa}'; +pub const LATTICE_DATA: char = '\u{e9fb}'; +pub const LAYER_ACTIVE: char = '\u{e9fc}'; +pub const LAYER_USED: char = '\u{e9fd}'; +pub const LIBRARY_DATA_BROKEN: char = '\u{e9fe}'; +pub const LIBRARY_DATA_DIRECT: char = '\u{e9ff}'; +pub const LIBRARY_DATA_INDIRECT: char = '\u{ea00}'; +pub const LIBRARY_DATA_OVERRIDE: char = '\u{ea01}'; +pub const LIGHT: char = '\u{ea02}'; +pub const LIGHT_AREA: char = '\u{ea03}'; +pub const LIGHT_DATA: char = '\u{ea04}'; +pub const LIGHT_HEMI: char = '\u{ea05}'; +pub const LIGHT_POINT: char = '\u{ea06}'; +pub const LIGHTPROBE_CUBEMAP: char = '\u{ea07}'; +pub const LIGHTPROBE_GRID: char = '\u{ea08}'; +pub const LIGHTPROBE_PLANAR: char = '\u{ea09}'; +pub const LIGHT_SPOT: char = '\u{ea0a}'; +pub const LIGHT_SUN: char = '\u{ea0b}'; +pub const LINEAR_CURVE: char = '\u{ea0c}'; +pub const LINE_DATA: char = '\u{ea0d}'; +pub const LINENUMBERS_OFF: char = '\u{ea0e}'; +pub const LINENUMBERS_ON: char = '\u{ea0f}'; +pub const LINK_BLEND: char = '\u{ea10}'; +pub const LINKED: char = '\u{ea11}'; +pub const LOCKED: char = '\u{ea12}'; +pub const LOCK_VIEW_OFF: char = '\u{ea13}'; +pub const LOCK_VIEW_ON: char = '\u{ea14}'; +pub const LONGDISPLAY: char = '\u{ea15}'; +pub const LOOP_BACK: char = '\u{ea16}'; +pub const LOOP_FORWARDS: char = '\u{ea17}'; +pub const MARKER: char = '\u{ea18}'; +pub const MARKER_HLT: char = '\u{ea19}'; +pub const MAT_CLOTH: char = '\u{ea1a}'; +pub const MAT_CUBE: char = '\u{ea1b}'; +pub const MATERIAL: char = '\u{ea1c}'; +pub const MATERIAL_DATA: char = '\u{ea1d}'; +pub const MAT_FLUID: char = '\u{ea1e}'; +pub const MAT_PLANE: char = '\u{ea1f}'; +pub const MAT_SHADERBALL: char = '\u{ea20}'; +pub const MAT_SPHERE: char = '\u{ea21}'; +pub const MAT_SPHERE_SKY: char = '\u{ea22}'; +pub const MEMORY: char = '\u{ea23}'; +pub const MENU_PANEL: char = '\u{ea24}'; +pub const MESH_CAPSULE: char = '\u{ea25}'; +pub const MESH_CIRCLE: char = '\u{ea26}'; +pub const MESH_CONE: char = '\u{ea27}'; +pub const MESH_CUBE: char = '\u{ea28}'; +pub const MESH_CYLINDER: char = '\u{ea29}'; +pub const MESH_DATA: char = '\u{ea2a}'; +pub const MESH_GRID: char = '\u{ea2b}'; +pub const MESH_ICOSPHERE: char = '\u{ea2c}'; +pub const MESH_MONKEY: char = '\u{ea2d}'; +pub const MESH_PLANE: char = '\u{ea2e}'; +pub const MESH_TORUS: char = '\u{ea2f}'; +pub const MESH_UVSPHERE: char = '\u{ea30}'; +pub const META_BALL: char = '\u{ea31}'; +pub const META_CAPSULE: char = '\u{ea32}'; +pub const META_CUBE: char = '\u{ea33}'; +pub const META_DATA: char = '\u{ea34}'; +pub const META_ELLIPSOID: char = '\u{ea35}'; +pub const META_PLANE: char = '\u{ea36}'; +pub const MOD_ARMATURE: char = '\u{ea37}'; +pub const MOD_ARRAY: char = '\u{ea38}'; +pub const MOD_BEVEL: char = '\u{ea39}'; +pub const MOD_BOOLEAN: char = '\u{ea3a}'; +pub const MOD_BUILD: char = '\u{ea3b}'; +pub const MOD_CAST: char = '\u{ea3c}'; +pub const MOD_CLOTH: char = '\u{ea3d}'; +pub const MOD_CURVE: char = '\u{ea3e}'; +pub const MOD_DATA_TRANSFER: char = '\u{ea3f}'; +pub const MOD_DECIM: char = '\u{ea40}'; +pub const MOD_DISPLACE: char = '\u{ea41}'; +pub const MOD_DYNAMICPAINT: char = '\u{ea42}'; +pub const MOD_EDGESPLIT: char = '\u{ea43}'; +pub const MOD_EXPLODE: char = '\u{ea44}'; +pub const MOD_FLUID: char = '\u{ea45}'; +pub const MOD_FLUIDSIM: char = '\u{ea46}'; +pub const MOD_HUE_SATURATION: char = '\u{ea47}'; +pub const MODIFIER: char = '\u{ea48}'; +pub const MODIFIER_DATA: char = '\u{ea49}'; +pub const MODIFIER_OFF: char = '\u{ea4a}'; +pub const MODIFIER_ON: char = '\u{ea4b}'; +pub const MOD_INSTANCE: char = '\u{ea4c}'; +pub const MOD_LATTICE: char = '\u{ea4d}'; +pub const MOD_MASK: char = '\u{ea4e}'; +pub const MOD_MESHDEFORM: char = '\u{ea4f}'; +pub const MOD_MIRROR: char = '\u{ea50}'; +pub const MOD_MULTIRES: char = '\u{ea51}'; +pub const MOD_NOISE: char = '\u{ea52}'; +pub const MOD_NORMALEDIT: char = '\u{ea53}'; +pub const MOD_OCEAN: char = '\u{ea54}'; +pub const MOD_OFFSET: char = '\u{ea55}'; +pub const MOD_OPACITY: char = '\u{ea56}'; +pub const MOD_PARTICLE_INSTANCE: char = '\u{ea57}'; +pub const MOD_PARTICLES: char = '\u{ea58}'; +pub const MOD_PHYSICS: char = '\u{ea59}'; +pub const MOD_REMESH: char = '\u{ea5a}'; +pub const MOD_SCREW: char = '\u{ea5b}'; +pub const MOD_SHRINKWRAP: char = '\u{ea5c}'; +pub const MOD_SIMPLEDEFORM: char = '\u{ea5d}'; +pub const MOD_SIMPLIFY: char = '\u{ea5e}'; +pub const MOD_SKIN: char = '\u{ea5f}'; +pub const MOD_SMOOTH: char = '\u{ea60}'; +pub const MOD_SOFT: char = '\u{ea61}'; +pub const MOD_SOLIDIFY: char = '\u{ea62}'; +pub const MOD_SUBSURF: char = '\u{ea63}'; +pub const MOD_THICKNESS: char = '\u{ea64}'; +pub const MOD_TIME: char = '\u{ea65}'; +pub const MOD_TINT: char = '\u{ea66}'; +pub const MOD_TRIANGULATE: char = '\u{ea67}'; +pub const MOD_UVPROJECT: char = '\u{ea68}'; +pub const MOD_VERTEX_WEIGHT: char = '\u{ea69}'; +pub const MOD_WARP: char = '\u{ea6a}'; +pub const MOD_WAVE: char = '\u{ea6b}'; +pub const MOD_WIREFRAME: char = '\u{ea6c}'; +pub const MONKEY: char = '\u{ea6d}'; +pub const MOUSE_LMB: char = '\u{ea6e}'; +pub const MOUSE_LMB_DRAG: char = '\u{ea6f}'; +pub const MOUSE_MMB: char = '\u{ea70}'; +pub const MOUSE_MMB_DRAG: char = '\u{ea71}'; +pub const MOUSE_MOVE: char = '\u{ea72}'; +pub const MOUSE_RMB: char = '\u{ea73}'; +pub const MOUSE_RMB_DRAG: char = '\u{ea74}'; +pub const MUTE_IPO_OFF: char = '\u{ea75}'; +pub const MUTE_IPO_ON: char = '\u{ea76}'; +pub const NETWORK_DRIVE: char = '\u{ea77}'; +pub const NEW_FOLDER: char = '\u{ea78}'; +pub const NEXT_KEYFRAME: char = '\u{ea79}'; +pub const NLA: char = '\u{ea7a}'; +pub const NLA_PUSHDOWN: char = '\u{ea7b}'; +pub const NO_CURVE: char = '\u{ea7c}'; +pub const NODE: char = '\u{ea7d}'; +pub const NODE_COMPOSITING: char = '\u{ea7e}'; +pub const NODE_CORNER: char = '\u{ea7f}'; +pub const NODE_INSERT_OFF: char = '\u{ea80}'; +pub const NODE_INSERT_ON: char = '\u{ea81}'; +pub const NODE_MATERIAL: char = '\u{ea82}'; +pub const NODE_SEL: char = '\u{ea83}'; +pub const NODE_SIDE: char = '\u{ea84}'; +pub const NODE_TEXTURE: char = '\u{ea85}'; +pub const NODE_TOP: char = '\u{ea86}'; +pub const NODETREE: char = '\u{ea87}'; +pub const NORMALIZE_FCURVES: char = '\u{ea88}'; +pub const NORMALS_FACE: char = '\u{ea89}'; +pub const NORMALS_VERTEX: char = '\u{ea8a}'; +pub const NORMALS_VERTEX_FACE: char = '\u{ea8b}'; +pub const OBJECT_DATA: char = '\u{ea8c}'; +pub const OBJECT_DATAMODE: char = '\u{ea8d}'; +pub const OBJECT_HIDDEN: char = '\u{ea8e}'; +pub const OBJECT_ORIGIN: char = '\u{ea8f}'; +pub const ONIONSKIN_OFF: char = '\u{ea90}'; +pub const ONIONSKIN_ON: char = '\u{ea91}'; +pub const OPTIONS: char = '\u{ea92}'; +pub const ORIENTATION_CURSOR: char = '\u{ea93}'; +pub const ORIENTATION_GIMBAL: char = '\u{ea94}'; +pub const ORIENTATION_GLOBAL: char = '\u{ea95}'; +pub const ORIENTATION_LOCAL: char = '\u{ea96}'; +pub const ORIENTATION_NORMAL: char = '\u{ea97}'; +pub const ORIENTATION_VIEW: char = '\u{ea98}'; +pub const ORPHAN_DATA: char = '\u{ea99}'; +pub const OUTLINER: char = '\u{ea9a}'; +pub const OUTLINER_COLLECTION: char = '\u{ea9b}'; +pub const OUTLINER_DATA_ARMATURE: char = '\u{ea9c}'; +pub const OUTLINER_DATA_CAMERA: char = '\u{ea9d}'; +pub const OUTLINER_DATA_CURVE: char = '\u{ea9e}'; +pub const OUTLINER_DATA_EMPTY: char = '\u{ea9f}'; +pub const OUTLINER_DATA_FONT: char = '\u{eaa0}'; +pub const OUTLINER_DATA_GP_LAYER: char = '\u{eaa1}'; +pub const OUTLINER_DATA_GREASEPENCIL: char = '\u{eaa2}'; +pub const OUTLINER_DATA_HAIR: char = '\u{eaa3}'; +pub const OUTLINER_DATA_LATTICE: char = '\u{eaa4}'; +pub const OUTLINER_DATA_LIGHT: char = '\u{eaa5}'; +pub const OUTLINER_DATA_LIGHTPROBE: char = '\u{eaa6}'; +pub const OUTLINER_DATA_MESH: char = '\u{eaa7}'; +pub const OUTLINER_DATA_META: char = '\u{eaa8}'; +pub const OUTLINER_DATA_POINTCLOUD: char = '\u{eaa9}'; +pub const OUTLINER_DATA_SPEAKER: char = '\u{eaaa}'; +pub const OUTLINER_DATA_SURFACE: char = '\u{eaab}'; +pub const OUTLINER_DATA_VOLUME: char = '\u{eaac}'; +pub const OUTLINER_OB_ARMATURE: char = '\u{eaad}'; +pub const OUTLINER_OB_CAMERA: char = '\u{eaae}'; +pub const OUTLINER_OB_CURVE: char = '\u{eaaf}'; +pub const OUTLINER_OB_EMPTY: char = '\u{eab0}'; +pub const OUTLINER_OB_FONT: char = '\u{eab1}'; +pub const OUTLINER_OB_FORCE_FIELD: char = '\u{eab2}'; +pub const OUTLINER_OB_GREASEPENCIL: char = '\u{eab3}'; +pub const OUTLINER_OB_GROUP_INSTANCE: char = '\u{eab4}'; +pub const OUTLINER_OB_HAIR: char = '\u{eab5}'; +pub const OUTLINER_OB_IMAGE: char = '\u{eab6}'; +pub const OUTLINER_OB_LATTICE: char = '\u{eab7}'; +pub const OUTLINER_OB_LIGHT: char = '\u{eab8}'; +pub const OUTLINER_OB_LIGHTPROBE: char = '\u{eab9}'; +pub const OUTLINER_OB_MESH: char = '\u{eaba}'; +pub const OUTLINER_OB_META: char = '\u{eabb}'; +pub const OUTLINER_OB_POINTCLOUD: char = '\u{eabc}'; +pub const OUTLINER_OB_SPEAKER: char = '\u{eabd}'; +pub const OUTLINER_OB_SURFACE: char = '\u{eabe}'; +pub const OUTLINER_OB_VOLUME: char = '\u{eabf}'; +pub const OUTPUT: char = '\u{eac0}'; +pub const OVERLAY: char = '\u{eac1}'; +pub const PACKAGE: char = '\u{eac2}'; +pub const PANEL_CLOSE: char = '\u{eac3}'; +pub const PARTICLE_DATA: char = '\u{eac4}'; +pub const PARTICLEMODE: char = '\u{eac5}'; +pub const PARTICLE_PATH: char = '\u{eac6}'; +pub const PARTICLE_POINT: char = '\u{eac7}'; +pub const PARTICLES: char = '\u{eac8}'; +pub const PARTICLE_TIP: char = '\u{eac9}'; +pub const PASTE_DOWN: char = '\u{eaca}'; +pub const PASTE_FLIP_DOWN: char = '\u{eacb}'; +pub const PASTE_FLIP_UP: char = '\u{eacc}'; +pub const PAUSE: char = '\u{eacd}'; +pub const PHYSICS: char = '\u{eace}'; +pub const PINNED: char = '\u{eacf}'; +pub const PIVOT_ACTIVE: char = '\u{ead0}'; +pub const PIVOT_CURSOR: char = '\u{ead1}'; +pub const PIVOT_INDIVIDUAL: char = '\u{ead2}'; +pub const PIVOT_MEDIAN: char = '\u{ead3}'; +pub const PIVOT_OBOUNDING_BOX: char = '\u{ead4}'; +pub const PLAY: char = '\u{ead5}'; +pub const PLAY_REVERSE: char = '\u{ead6}'; +pub const PLAY_SOUND: char = '\u{ead7}'; +pub const PLUGIN: char = '\u{ead8}'; +pub const PLUS: char = '\u{ead9}'; +pub const PMARKER: char = '\u{eada}'; +pub const PMARKER_ACT: char = '\u{eadb}'; +pub const PMARKER_SEL: char = '\u{eadc}'; +pub const POINTCLOUD_DATA: char = '\u{eadd}'; +pub const POSE_HLT: char = '\u{eade}'; +pub const PREFERENCES: char = '\u{eadf}'; +pub const PRESET: char = '\u{eae0}'; +pub const PRESET_NEW: char = '\u{eae1}'; +pub const PREVIEW_RANGE: char = '\u{eae2}'; +pub const PREV_KEYFRAME: char = '\u{eae3}'; +pub const PROPERTIES: char = '\u{eae4}'; +pub const PROPORTIONAL_CENTER_ON: char = '\u{eae5}'; +pub const PROPORTIONAL_OFF: char = '\u{eae6}'; +pub const PROPORTIONAL_ON: char = '\u{eae7}'; +pub const PROPORTIONAL_PROJECTED: char = '\u{eae8}'; +pub const QUESTION: char = '\u{eae9}'; +pub const QUIT: char = '\u{eaea}'; +pub const RADIOBUT_OFF: char = '\u{eaeb}'; +pub const RADIOBUT_ON: char = '\u{eaec}'; +pub const REC: char = '\u{eaed}'; +pub const RECOVER_LAST: char = '\u{eaee}'; +pub const REMOVE: char = '\u{eaef}'; +pub const RENDER_ANIMATION: char = '\u{eaf0}'; +pub const RENDERLAYERS: char = '\u{eaf1}'; +pub const RENDER_RESULT: char = '\u{eaf2}'; +pub const RENDER_STILL: char = '\u{eaf3}'; +pub const RESTRICT_COLOR_OFF: char = '\u{eaf4}'; +pub const RESTRICT_COLOR_ON: char = '\u{eaf5}'; +pub const RESTRICT_INSTANCED_OFF: char = '\u{eaf6}'; +pub const RESTRICT_INSTANCED_ON: char = '\u{eaf7}'; +pub const RESTRICT_RENDER_OFF: char = '\u{eaf8}'; +pub const RESTRICT_RENDER_ON: char = '\u{eaf9}'; +pub const RESTRICT_SELECT_OFF: char = '\u{eafa}'; +pub const RESTRICT_SELECT_ON: char = '\u{eafb}'; +pub const RESTRICT_VIEW_OFF: char = '\u{eafc}'; +pub const RESTRICT_VIEW_ON: char = '\u{eafd}'; +pub const REW: char = '\u{eafe}'; +pub const RIGHTARROW: char = '\u{eaff}'; +pub const RIGHTARROW_THIN: char = '\u{eb00}'; +pub const RIGID_BODY: char = '\u{eb01}'; +pub const RIGID_BODY_CONSTRAINT: char = '\u{eb02}'; +pub const RNA: char = '\u{eb03}'; +pub const RNA_ADD: char = '\u{eb04}'; +pub const ROOT_CURVE: char = '\u{eb05}'; +pub const ROUND_CURVE: char = '\u{eb06}'; +pub const SCENE: char = '\u{eb07}'; +pub const SCENE_DATA: char = '\u{eb08}'; +pub const SCREEN_BACK: char = '\u{eb09}'; +pub const SCRIPT: char = '\u{eb0a}'; +pub const SCRIPT_PLUGINS: char = '\u{eb0b}'; +pub const SCULPTMODE_HLT: char = '\u{eb0c}'; +pub const SELECT_DIFFERENCE: char = '\u{eb0d}'; +pub const SELECT_EXTEND: char = '\u{eb0e}'; +pub const SELECT_INTERSECT: char = '\u{eb0f}'; +pub const SELECT_SET: char = '\u{eb10}'; +pub const SELECT_SUBTRACT: char = '\u{eb11}'; +pub const SEQ_CHROMA_SCOPE: char = '\u{eb12}'; +pub const SEQ_HISTOGRAM: char = '\u{eb13}'; +pub const SEQ_LUMA_WAVEFORM: char = '\u{eb14}'; +pub const SEQ_PREVIEW: char = '\u{eb15}'; +pub const SEQ_SEQUENCER: char = '\u{eb16}'; +pub const SEQ_SPLITVIEW: char = '\u{eb17}'; +pub const SEQ_STRIP_DUPLICATE: char = '\u{eb18}'; +pub const SEQ_STRIP_META: char = '\u{eb19}'; +pub const SEQUENCE: char = '\u{eb1a}'; +pub const SETTINGS: char = '\u{eb1b}'; +pub const SHADERFX: char = '\u{eb1c}'; +pub const SHADING_BOUNDING_BOX: char = '\u{eb1d}'; +pub const SHADING_RENDERED: char = '\u{eb1e}'; +pub const SHADING_SOLID: char = '\u{eb1f}'; +pub const SHADING_TEXTURE: char = '\u{eb20}'; +pub const SHADING_WIRE: char = '\u{eb21}'; +pub const SHAPEKEY_DATA: char = '\u{eb22}'; +pub const SHARP_CURVE: char = '\u{eb23}'; +pub const SHORTDISPLAY: char = '\u{eb24}'; +pub const SMALL_CAPS: char = '\u{eb25}'; +pub const SMOOTH_CURVE: char = '\u{eb26}'; +pub const SNAP_EDGE: char = '\u{eb27}'; +pub const SNAP_FACE: char = '\u{eb28}'; +pub const SNAP_FACE_CENTER: char = '\u{eb29}'; +pub const SNAP_GRID: char = '\u{eb2a}'; +pub const SNAP_INCREMENT: char = '\u{eb2b}'; +pub const SNAP_MIDPOINT: char = '\u{eb2c}'; +pub const SNAP_NORMAL: char = '\u{eb2d}'; +pub const SNAP_OFF: char = '\u{eb2e}'; +pub const SNAP_ON: char = '\u{eb2f}'; +pub const SNAP_PEEL_OBJECT: char = '\u{eb30}'; +pub const SNAP_PERPENDICULAR: char = '\u{eb31}'; +pub const SNAP_VERTEX: char = '\u{eb32}'; +pub const SNAP_VOLUME: char = '\u{eb33}'; +pub const SOLO_OFF: char = '\u{eb34}'; +pub const SOLO_ON: char = '\u{eb35}'; +pub const SORT_ALPHA: char = '\u{eb36}'; +pub const SORT_ASC: char = '\u{eb37}'; +pub const SORT_BY_EXT: char = '\u{eb38}'; +pub const SORT_DESC: char = '\u{eb39}'; +pub const SORT_SIZE: char = '\u{eb3a}'; +pub const SORT_TIME: char = '\u{eb3b}'; +pub const SOUND: char = '\u{eb3c}'; +pub const SPEAKER: char = '\u{eb3d}'; +pub const SPHERE: char = '\u{eb3e}'; +pub const SPHERE_CURVE: char = '\u{eb3f}'; +pub const SPREADSHEET: char = '\u{eb40}'; +pub const STATUSBAR: char = '\u{eb41}'; +pub const STICKY_UVS_DISABLE: char = '\u{eb42}'; +pub const STICKY_UVS_LOC: char = '\u{eb43}'; +pub const STICKY_UVS_VERT: char = '\u{eb44}'; +pub const STRANDS: char = '\u{eb45}'; +pub const STROKE: char = '\u{eb46}'; +pub const STYLUS_PRESSURE: char = '\u{eb47}'; +pub const SURFACE_DATA: char = '\u{eb48}'; +pub const SURFACE_NCIRCLE: char = '\u{eb49}'; +pub const SURFACE_NCURVE: char = '\u{eb4a}'; +pub const SURFACE_NCYLINDER: char = '\u{eb4b}'; +pub const SURFACE_NSPHERE: char = '\u{eb4c}'; +pub const SURFACE_NSURFACE: char = '\u{eb4d}'; +pub const SURFACE_NTORUS: char = '\u{eb4e}'; +pub const SYNTAX_OFF: char = '\u{eb4f}'; +pub const SYNTAX_ON: char = '\u{eb50}'; +pub const SYSTEM: char = '\u{eb51}'; +pub const TEMP: char = '\u{eb52}'; +pub const TEXT: char = '\u{eb53}'; +pub const TEXTURE: char = '\u{eb54}'; +pub const TEXTURE_DATA: char = '\u{eb55}'; +pub const THREE_DOTS: char = '\u{eb56}'; +pub const TIME: char = '\u{eb57}'; +pub const TOOL_SETTINGS: char = '\u{eb58}'; +pub const TOPBAR: char = '\u{eb59}'; +pub const TPAINT_HLT: char = '\u{eb5a}'; +pub const TRACKER: char = '\u{eb5b}'; +pub const TRACKER_DATA: char = '\u{eb5c}'; +pub const TRACKING: char = '\u{eb5d}'; +pub const TRACKING_BACKWARDS: char = '\u{eb5e}'; +pub const TRACKING_BACKWARDS_SINGLE: char = '\u{eb5f}'; +pub const TRACKING_CLEAR_BACKWARDS: char = '\u{eb60}'; +pub const TRACKING_CLEAR_FORWARDS: char = '\u{eb61}'; +pub const TRACKING_FORWARDS: char = '\u{eb62}'; +pub const TRACKING_FORWARDS_SINGLE: char = '\u{eb63}'; +pub const TRACKING_REFINE_BACKWARDS: char = '\u{eb64}'; +pub const TRACKING_REFINE_FORWARDS: char = '\u{eb65}'; +pub const TRANSFORM_ORIGINS: char = '\u{eb66}'; +pub const TRASH: char = '\u{eb67}'; +pub const TRIA_DOWN: char = '\u{eb68}'; +pub const TRIA_DOWN_BAR: char = '\u{eb69}'; +pub const TRIA_LEFT: char = '\u{eb6a}'; +pub const TRIA_LEFT_BAR: char = '\u{eb6b}'; +pub const TRIA_RIGHT: char = '\u{eb6c}'; +pub const TRIA_RIGHT_BAR: char = '\u{eb6d}'; +pub const TRIA_UP: char = '\u{eb6e}'; +pub const TRIA_UP_BAR: char = '\u{eb6f}'; +pub const UGLYPACKAGE: char = '\u{eb70}'; +pub const UNDERLINE: char = '\u{eb71}'; +pub const UNLINKED: char = '\u{eb72}'; +pub const UNLOCKED: char = '\u{eb73}'; +pub const UNPINNED: char = '\u{eb74}'; +pub const URL: char = '\u{eb75}'; +pub const USER: char = '\u{eb76}'; +pub const UV: char = '\u{eb77}'; +pub const UV_DATA: char = '\u{eb78}'; +pub const UV_EDGE_SELECT: char = '\u{eb79}'; +pub const UV_FACE_SELECT: char = '\u{eb7a}'; +pub const UV_ISLANDS_SELECT: char = '\u{eb7b}'; +pub const UV_SYNC_SELECT: char = '\u{eb7c}'; +pub const UV_VERTEX_SELECT: char = '\u{eb7d}'; +pub const VERTEX_SELECT: char = '\u{eb7e}'; +pub const VIEW3D: char = '\u{eb7f}'; +pub const VIEW_CAMERA: char = '\u{eb80}'; +pub const VIEW_ORTHO: char = '\u{eb81}'; +pub const VIEW_PAN: char = '\u{eb82}'; +pub const VIEW_PERSPECTIVE: char = '\u{eb83}'; +pub const VIEW_ZOOM: char = '\u{eb84}'; +pub const VIEWZOOM: char = '\u{eb85}'; +pub const VISUAL_SELECTION_DISABLED: char = '\u{eb86}'; +pub const VISUAL_SELECTION_HIDDEN: char = '\u{eb87}'; +pub const VISUAL_SELECTION_SELECTABLE: char = '\u{eb88}'; +pub const VISUAL_SELECTION_UNSELECTABLE: char = '\u{eb89}'; +pub const VOLUME_DATA: char = '\u{eb8a}'; +pub const VPAINT_HLT: char = '\u{eb8b}'; +pub const WINDOW: char = '\u{eb8c}'; +pub const WORDWRAP_OFF: char = '\u{eb8d}'; +pub const WORDWRAP_ON: char = '\u{eb8e}'; +pub const WORKSPACE: char = '\u{eb8f}'; +pub const WORLD: char = '\u{eb90}'; +pub const WORLD_DATA: char = '\u{eb91}'; +pub const WPAINT_HLT: char = '\u{eb92}'; +pub const X: char = '\u{eb93}'; +pub const X_RAY: char = '\u{eb94}'; +pub const ZOOM_ALL: char = '\u{eb95}'; +pub const ZOOM_IN: char = '\u{eb96}'; +pub const ZOOM_OUT: char = '\u{eb97}'; +pub const ZOOM_PREVIOUS: char = '\u{eb98}'; +pub const ZOOM_SELECTED: char = '\u{eb99}'; diff --git a/retrotool-gui/src/loaders.rs b/retrotool-gui/src/loaders.rs index 65dc7a7..406113e 100644 --- a/retrotool-gui/src/loaders.rs +++ b/retrotool-gui/src/loaders.rs @@ -9,16 +9,19 @@ use astc_decode::{astc_decode, Footprint}; use bevy::{ app::{App, Plugin}, asset::{ - AddAsset, AssetIo, AssetIoError, AssetLoader, BoxedFuture, LoadContext, LoadedAsset, - Metadata, + AddAsset, AssetIo, AssetIoError, AssetLoader, AssetPath, BoxedFuture, LoadContext, + LoadedAsset, Metadata, }, prelude::*, + utils::HashMap, }; use binrw::Endian; use image::RgbaImage; use retrolib::{ format::{ + cmdl::{CMaterialDataInner, ModelData}, foot::locate_meta, + mtrl::MaterialData, pack::{Package, SparsePackageEntry}, txtr::TextureData, }, @@ -61,6 +64,7 @@ impl AssetIo for RetroAssetIo { if let Some(package) = packages.iter().find(|p| p.entries.iter().any(|e| e.id == id)) { + println!("Loading {} from {}", id, package.path.display()); package_path = Some(package.path.clone()); } } @@ -227,3 +231,108 @@ impl AssetLoader for TextureAssetLoader { fn extensions(&self) -> &[&str] { &["txtr"] } } + +#[derive(Debug, Clone, bevy::reflect::TypeUuid)] +#[uuid = "83269869-1209-408e-8835-bc6f2496e829"] +pub struct ModelAsset { + pub inner: ModelData, + pub textures: HashMap>, +} + +pub struct ModelAssetLoader; + +impl Plugin for ModelAssetLoader { + fn build(&self, app: &mut App) { + app.add_asset::().add_asset_loader(ModelAssetLoader); + } +} + +impl AssetLoader for ModelAssetLoader { + fn load<'a>( + &'a self, + bytes: &'a [u8], + load_context: &'a mut LoadContext, + ) -> BoxedFuture<'a, anyhow::Result<(), Error>> { + Box::pin(async move { + let meta = locate_meta(bytes, Endian::Little)?; + let data = ModelData::slice(bytes, meta, Endian::Little)?; + println!("Loaded model {:?}", data.head); + let mut dependencies = HashMap::::new(); + for mat in &data.mtrl.materials { + for data in &mat.data { + match &data.data { + CMaterialDataInner::Texture(texture) => { + dependencies.insert( + texture.id, + AssetPath::new(PathBuf::from(format!("{}.TXTR", texture.id)), None), + ); + } + CMaterialDataInner::LayeredTexture(texture) => { + for texture in &texture.textures { + if texture.id.is_nil() { + continue; + } + dependencies.insert( + texture.id, + AssetPath::new( + PathBuf::from(format!("{}.TXTR", texture.id)), + None, + ), + ); + } + } + _ => {} + } + } + } + let textures = dependencies + .iter() + .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()), + ); + Ok(()) + }) + } + + fn extensions(&self) -> &[&str] { &["cmdl"] } +} + +#[derive(Debug, Clone, bevy::reflect::TypeUuid)] +#[uuid = "83269869-1209-408e-8835-bc6f2496e82a"] +pub struct MaterialAsset { + pub inner: ModelData, +} + +pub struct MaterialAssetLoader; + +impl Plugin for MaterialAssetLoader { + fn build(&self, app: &mut App) { + app.add_asset::().add_asset_loader(MaterialAssetLoader); + } +} + +impl AssetLoader for MaterialAssetLoader { + fn load<'a>( + &'a self, + bytes: &'a [u8], + _load_context: &'a mut LoadContext, + ) -> BoxedFuture<'a, anyhow::Result<(), Error>> { + Box::pin(async move { + let meta = locate_meta(bytes, Endian::Little)?; + // let (desc, data, remain) = FormDescriptor::slice(bytes, Endian::Little)?; + // println!("Loading material {:?}", desc); + let _mtrl = MaterialData::slice(bytes, meta, Endian::Little)?; + // fs::write("mtrl.out", &mtrl.decompressed)?; + // load_context.set_default_asset( + // LoadedAsset::new(ModelAsset { inner: data, textures }) + // .with_dependencies(dependencies.into_values().collect()), + // ); + Ok(()) + }) + } + + fn extensions(&self) -> &[&str] { &["mtrl"] } +} diff --git a/retrotool-gui/src/main.rs b/retrotool-gui/src/main.rs index 35c1681..5116ca6 100644 --- a/retrotool-gui/src/main.rs +++ b/retrotool-gui/src/main.rs @@ -1,34 +1,22 @@ +mod icon; mod loaders; +mod tabs; use std::path::PathBuf; -use bevy::{ - asset::LoadState, - ecs::system::{ - lifetimeless::{SRes, SResMut}, - SystemParam, SystemParamItem, SystemState, - }, - prelude::*, - render::render_resource::{ - Extent3d, TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, - }, -}; -use bevy_egui::{ - egui, - egui::{text::LayoutJob, Color32, FontId, TextFormat, Widget}, - EguiContext, EguiPlugin, -}; -use egui::{TextureId, Ui, WidgetText}; -use retrolib::format::{ - txtr::{ETextureFormat, ETextureType, K_FORM_TXTR}, - FourCC, -}; +use bevy::{app::AppExit, prelude::*}; +use bevy_egui::{egui, EguiContext, EguiPlugin}; +use egui::{FontFamily, FontId}; +use retrolib::format::FourCC; use uuid::Uuid; use walkdir::{DirEntry, WalkDir}; -use crate::loaders::{ - package_loader_system, PackageAssetLoader, PackageDirectory, RetroAssetIoPlugin, TextureAsset, - TextureAssetLoader, +use crate::{ + loaders::{ + package_loader_system, MaterialAssetLoader, ModelAssetLoader, PackageAssetLoader, + PackageDirectory, RetroAssetIoPlugin, TextureAssetLoader, + }, + tabs::{load_tab, model::TemporaryTag, project::ProjectTab, TabState, TabType, TabViewer}, }; #[derive(Default, Resource)] @@ -40,395 +28,65 @@ fn main() { file_open.0.push(arg.into()); } App::new() - .insert_resource(ClearColor(Color::rgb(0.0, 0.0, 0.0))) - .insert_resource(Msaa { samples: 1 }) + .insert_resource(ClearColor(Color::rgb(0.05, 0.05, 0.05))) + .insert_resource(Msaa { samples: 4 }) .insert_resource(bevy::render::settings::WgpuSettings { features: bevy::render::settings::WgpuFeatures::TEXTURE_COMPRESSION_BC, - ..Default::default() + ..default() }) + .insert_resource(bevy::winit::WinitSettings::desktop_app()) .insert_resource(file_open) .init_resource::() .init_resource::() - .add_plugins(DefaultPlugins.build().add_before::(RetroAssetIoPlugin)) + .add_plugins( + DefaultPlugins + .build() + .set(WindowPlugin { + window: WindowDescriptor { + width: 1600.0, + height: 900.0, + position: WindowPosition::Centered, + title: "retrotool".to_string(), + ..default() + }, + ..default() + }) + .add_before::(RetroAssetIoPlugin), + ) .add_plugin(PackageAssetLoader) .add_plugin(TextureAssetLoader) + .add_plugin(ModelAssetLoader) + .add_plugin(MaterialAssetLoader) .add_plugin(EguiPlugin) - .add_system(file_drop) - .add_system(load_files) - .add_system(package_loader_system) - .add_system(ui_system) + .add_startup_system(setup_icon_font) + .add_system_to_stage(CoreStage::PreUpdate, file_drop) + .add_system_to_stage(CoreStage::PreUpdate, load_files) + .add_system_to_stage(CoreStage::PreUpdate, package_loader_system) + .add_system_to_stage(CoreStage::PreUpdate, ui_system.before_commands()) .run(); } #[derive(Debug, Clone, Default, Eq, PartialEq)] -struct AssetRef { +pub struct AssetRef { id: Uuid, kind: FourCC, } -enum TabType { - Project(ProjectTab), - Texture(TextureTab), - Empty, -} - #[derive(Resource)] struct UiState { tree: egui_dock::Tree, + ui_font: FontId, + code_font: FontId, } impl Default for UiState { fn default() -> Self { let mut tree = egui_dock::Tree::new(vec![TabType::Empty]); - tree.split_left(egui_dock::NodeIndex::root(), 0.3, vec![TabType::Project(ProjectTab)]); - Self { tree } - } -} - -struct TabViewer<'a> { - world: &'a mut World, - state: TabState, -} - -struct TabState { - open_assets: Vec, - open_tab: Option, -} - -trait SystemTab { - type LoadParam: SystemParam; - type UiParam: SystemParam; - - fn load(&mut self, _ctx: &mut EguiContext, _query: SystemParamItem<'_, '_, Self::LoadParam>) {} - - fn close(&mut self, _ctx: &mut EguiContext, _query: SystemParamItem<'_, '_, Self::LoadParam>) {} - - fn ui( - &mut self, - ui: &mut Ui, - query: SystemParamItem<'_, '_, Self::UiParam>, - state: &mut TabState, - ); - - fn title(&mut self) -> WidgetText; -} - -struct ProjectTab; - -impl SystemTab for ProjectTab { - type LoadParam = (); - type UiParam = (SRes, SRes>); - - fn ui( - &mut self, - ui: &mut Ui, - query: SystemParamItem<'_, '_, Self::UiParam>, - state: &mut TabState, - ) { - let (server, packages) = query; - let mut packages_sorted = - packages.iter().map(|(_, p)| p).collect::>(); - packages_sorted.sort_by_key(|p| &p.name); - for package in packages_sorted { - egui::CollapsingHeader::new(&package.name).show(ui, |ui| { - for entry in &package.entries { - let mut job = LayoutJob::simple( - format!("{} {}", entry.kind, entry.id), - FontId::monospace(12.0), - Color32::GRAY, - 0.0, - ); - if let Some(name) = &entry.name { - job.append( - &format!("\n{name}"), - 0.0, - TextFormat::simple(FontId::monospace(12.0), Color32::WHITE), - ); - } - let asset_ref = AssetRef { id: entry.id, kind: entry.kind }; - if egui::SelectableLabel::new(state.open_assets.contains(&asset_ref), job) - .ui(ui) - .clicked() - { - #[allow(clippy::single_match)] - match entry.kind { - K_FORM_TXTR => { - let handle = server.load::(format!( - "{}.{}", - entry.id, entry.kind - )); - state.open_tab = Some(TabType::Texture(TextureTab { - asset_ref: asset_ref.clone(), - handle, - loaded_texture: None, - })); - } - _ => {} - } - } - } - }); - } - } - - fn title(&mut self) -> WidgetText { "Browser".into() } -} - -struct LoadedTexture { - texture_ids: Vec, -} - -struct TextureTab { - asset_ref: AssetRef, - handle: Handle, - loaded_texture: Option, -} - -impl SystemTab for TextureTab { - type LoadParam = (SRes>, SResMut>); - type UiParam = (SRes, SRes>); - - fn load(&mut self, ctx: &mut EguiContext, query: SystemParamItem<'_, '_, Self::LoadParam>) { - if self.loaded_texture.is_some() { - return; - } - - let (textures, mut images) = query; - let Some(txtr) = textures.get(&self.handle) else { return; }; - let mut texture_ids = Vec::new(); - if let Some(rgba) = &txtr.rgba { - let image_handle = images.add(Image { - data: rgba.clone(), - texture_descriptor: TextureDescriptor { - label: None, - size: Extent3d { - width: txtr.inner.head.width, - height: txtr.inner.head.height, - depth_or_array_layers: 1, - }, - mip_level_count: 1, - sample_count: 1, - dimension: TextureDimension::D2, - format: if txtr.inner.head.format.is_srgb() { - TextureFormat::Rgba8UnormSrgb - } else { - TextureFormat::Rgba8Unorm - }, - usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, - }, - sampler_descriptor: Default::default(), - texture_view_descriptor: None, - }); - texture_ids.push(ctx.add_image(image_handle)); - } else { - let array_stride: usize = - (txtr.inner.head.mip_sizes.iter().sum::() / txtr.inner.head.layers) as usize; - for layer in 0..txtr.inner.head.layers as usize { - let image_handle = images.add(Image { - data: txtr.inner.data - [layer * array_stride..(layer * array_stride) + array_stride] - .to_vec(), - texture_descriptor: TextureDescriptor { - label: None, - size: Extent3d { - width: txtr.inner.head.width, - height: txtr.inner.head.height, - depth_or_array_layers: 1, - }, - mip_level_count: txtr.inner.head.mip_sizes.len() as u32, - sample_count: 1, - dimension: match txtr.inner.head.kind { - ETextureType::_1D => TextureDimension::D1, - ETextureType::_2D => TextureDimension::D2, - ETextureType::_3D => TextureDimension::D3, - ETextureType::Cube => TextureDimension::D2, - ETextureType::_1DArray => TextureDimension::D1, - ETextureType::_2DArray => TextureDimension::D2, - ETextureType::_2DMultisample => TextureDimension::D2, - ETextureType::_2DMultisampleArray => TextureDimension::D2, - ETextureType::CubeArray => TextureDimension::D2, - }, - format: match txtr.inner.head.format { - ETextureFormat::R8Unorm => TextureFormat::Rgba8Unorm, - ETextureFormat::R8Snorm => TextureFormat::R8Snorm, - ETextureFormat::R8Uint => TextureFormat::R8Uint, - ETextureFormat::R8Sint => TextureFormat::R8Sint, - ETextureFormat::R16Unorm => TextureFormat::R16Unorm, - ETextureFormat::R16Snorm => TextureFormat::R16Snorm, - ETextureFormat::R16Uint => TextureFormat::R16Uint, - ETextureFormat::R16Sint => TextureFormat::R16Sint, - ETextureFormat::R16Float => TextureFormat::R16Float, - ETextureFormat::R32Uint => TextureFormat::R32Uint, - ETextureFormat::R32Sint => TextureFormat::R32Sint, - ETextureFormat::Rgba8Unorm => TextureFormat::Rgba8Unorm, - ETextureFormat::Rgba8Srgb => TextureFormat::Rgba8UnormSrgb, - ETextureFormat::Rgba16Float => TextureFormat::Rgba16Float, - ETextureFormat::Rgba32Float => TextureFormat::Rgba32Float, - ETextureFormat::Depth16Unorm => TextureFormat::Depth16Unorm, - ETextureFormat::Depth16Unorm2 => TextureFormat::Depth16Unorm, - ETextureFormat::Depth24S8Unorm => TextureFormat::Depth24PlusStencil8, - ETextureFormat::Depth32Float => TextureFormat::Depth32Float, - ETextureFormat::RgbaBc1Unorm => TextureFormat::Bc1RgbaUnorm, - ETextureFormat::RgbaBc1Srgb => TextureFormat::Bc1RgbaUnormSrgb, - ETextureFormat::RgbaBc2Unorm => TextureFormat::Bc2RgbaUnorm, - ETextureFormat::RgbaBc2Srgb => TextureFormat::Bc2RgbaUnormSrgb, - ETextureFormat::RgbaBc3Unorm => TextureFormat::Bc3RgbaUnorm, - ETextureFormat::RgbaBc3Srgb => TextureFormat::Bc3RgbaUnormSrgb, - ETextureFormat::RgbaBc4Unorm => TextureFormat::Bc4RUnorm, - ETextureFormat::RgbaBc4Snorm => TextureFormat::Bc4RSnorm, - ETextureFormat::RgbaBc5Unorm => TextureFormat::Bc5RgUnorm, - ETextureFormat::RgbaBc5Snorm => TextureFormat::Bc5RgSnorm, - ETextureFormat::Rg11B10Float => TextureFormat::Rg11b10Float, - ETextureFormat::R32Float => TextureFormat::R32Float, - ETextureFormat::Rg8Unorm => TextureFormat::Rg8Unorm, - ETextureFormat::Rg8Snorm => TextureFormat::Rg8Snorm, - ETextureFormat::Rg8Uint => TextureFormat::Rg8Uint, - ETextureFormat::Rg8Sint => TextureFormat::Rg8Sint, - ETextureFormat::Rg16Float => TextureFormat::Rg16Float, - ETextureFormat::Rg16Unorm => TextureFormat::Rg16Unorm, - ETextureFormat::Rg16Snorm => TextureFormat::Rg16Snorm, - ETextureFormat::Rg16Uint => TextureFormat::Rg16Uint, - ETextureFormat::Rg16Sint => TextureFormat::Rg16Sint, - ETextureFormat::Rgb10A2Unorm => TextureFormat::Rgb10a2Unorm, - ETextureFormat::Rg32Uint => TextureFormat::Rg32Uint, - ETextureFormat::Rg32Sint => TextureFormat::Rg32Sint, - ETextureFormat::Rg32Float => TextureFormat::Rg32Float, - ETextureFormat::Rgba16Unorm => TextureFormat::Rgba16Unorm, - ETextureFormat::Rgba16Snorm => TextureFormat::Rgba16Snorm, - ETextureFormat::Rgba16Uint => TextureFormat::Rgba16Uint, - ETextureFormat::Rgba16Sint => TextureFormat::Rgba16Sint, - ETextureFormat::Rgba32Uint => TextureFormat::Rgba32Uint, - ETextureFormat::Rgba32Sint => TextureFormat::Rgba32Sint, - ETextureFormat::BptcUfloat => TextureFormat::Bc6hRgbUfloat, - ETextureFormat::BptcSfloat => TextureFormat::Bc6hRgbSfloat, - ETextureFormat::BptcUnorm => TextureFormat::Bc7RgbaUnorm, - ETextureFormat::BptcUnormSrgb => TextureFormat::Bc7RgbaUnormSrgb, - _ => todo!(), - }, - usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, - }, - sampler_descriptor: Default::default(), - texture_view_descriptor: None, - }); - texture_ids.push(ctx.add_image(image_handle)); - } - }; - self.loaded_texture = Some(LoadedTexture { texture_ids }); - } - - fn ui( - &mut self, - ui: &mut Ui, - query: SystemParamItem<'_, '_, Self::UiParam>, - _state: &mut TabState, - ) { - let (server, textures) = 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(Color32::RED, "Loading failed"); - return; - } - LoadState::Unloaded => { - return; - } - }; - - let loaded = self.loaded_texture.as_mut().unwrap(); - if let Some(txtr) = textures.get(&self.handle) { - ui.label(format!("Type: {:?}", txtr.inner.head.kind)); - ui.label(format!("Format: {:?}", txtr.inner.head.format)); - ui.label(format!( - "Dimensions: {}x{}x{} (mips: {})", - txtr.inner.head.width, - txtr.inner.head.height, - txtr.inner.head.layers, - txtr.inner.head.mip_sizes.len() - )); - if txtr.inner.head.kind == ETextureType::Cube && loaded.texture_ids.len() == 6 { - let width = txtr.inner.head.width; - let height = txtr.inner.head.height; - let (_, rect) = - ui.allocate_space(egui::Vec2 { x: (width * 4) as f32, y: (height * 3) as f32 }); - let size = egui::Vec2 { x: width as f32, y: height as f32 }; - let mut draw_image = |i: usize, x: u32, y: u32| { - let min = egui::Vec2 { x: (width * x) as f32, y: (height * y) as f32 }; - let max = - egui::Vec2 { x: (width * (x + 1)) as f32, y: (height * (y + 1)) as f32 }; - egui::widgets::Image::new(loaded.texture_ids[i], size) - .paint_at(ui, egui::Rect { min: rect.min + min, max: rect.min + max }); - }; - draw_image(2, 1, 0); - draw_image(1, 0, 1); - draw_image(4, 1, 1); - draw_image(0, 2, 1); - draw_image(5, 3, 1); - draw_image(3, 1, 2); - } else { - for image in &loaded.texture_ids { - ui.add(egui::widgets::Image::new(*image, egui::Vec2 { - x: txtr.inner.head.width as f32, - y: txtr.inner.head.height as f32, - })); - } - } - } - } - - fn title(&mut self) -> WidgetText { - format!("{} {}", self.asset_ref.kind, self.asset_ref.id).into() - } -} - -fn load_tab(world: &mut World, ctx: &mut EguiContext, tab: &mut T) { - let mut state: SystemState = SystemState::new(world); - tab.load(ctx, state.get_mut(world)); -} - -fn render_tab( - world: &mut World, - ui: &mut Ui, - tab: &mut T, - tab_state: &mut TabState, -) { - let mut state: SystemState = SystemState::new(world); - tab.ui(ui, state.get_mut(world), tab_state); -} - -impl egui_dock::TabViewer for TabViewer<'_> { - type Tab = TabType; - - fn ui(&mut self, ui: &mut Ui, tab: &mut Self::Tab) { - match tab { - 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::Empty => {} - } - } - - fn title(&mut self, tab: &mut Self::Tab) -> WidgetText { - match tab { - TabType::Project(tab) => tab.title(), - TabType::Texture(tab) => tab.title(), - TabType::Empty => "Placeholder".into(), - } - } - - fn on_close(&mut self, tab: &mut Self::Tab) -> bool { - match tab { - TabType::Project(_) => false, - TabType::Texture(_) => true, - TabType::Empty => false, + tree.split_left(egui_dock::NodeIndex::root(), 0.25, vec![TabType::Project(ProjectTab)]); + Self { + tree, + ui_font: FontId { size: 13.0, family: FontFamily::Proportional }, + code_font: FontId { size: 14.0, family: FontFamily::Monospace }, } } } @@ -476,7 +134,7 @@ fn ui_system(world: &mut World) { egui::menu::bar(ui, |ui| { egui::menu::menu_button(ui, "File", |ui| { if ui.button("Quit").clicked() { - std::process::exit(0); + world.send_event(AppExit); } }); }); @@ -495,14 +153,34 @@ fn ui_system(world: &mut World) { load_tab(world, egui_ctx.as_mut(), tab); tab_assets.push(tab.asset_ref.clone()); } + TabType::Model(tab) => { + load_tab(world, egui_ctx.as_mut(), tab); + tab_assets.push(tab.asset_ref.clone()); + } TabType::Empty => {} } } } } - let mut viewer = - TabViewer { world, state: TabState { open_assets: tab_assets, open_tab: None } }; + // Remove all temporary entities + let mut to_remove = vec![]; + for (entity, _) in world.query::<(Entity, With)>().iter(world) { + to_remove.push(entity); + } + for entity in to_remove { + world.despawn(entity); + } + + let mut viewer = TabViewer { + world, + state: TabState { + open_assets: tab_assets, + open_tab: None, + viewport: default(), + render_layer: 0, + }, + }; egui_dock::DockArea::new(&mut ui_state.tree) .style(egui_dock::Style::from_egui(egui_ctx.ctx_mut().style().as_ref())) .show(egui_ctx.ctx_mut(), &mut viewer); @@ -510,6 +188,38 @@ fn ui_system(world: &mut World) { if let Some(tab) = viewer.state.open_tab { ui_state.tree.push_to_first_leaf(tab); } + + if viewer.state.render_layer == 0 { + // Spawn a camera to just clear the screen + world.spawn((Camera3dBundle::default(), TemporaryTag)); + } }); }); } + +fn setup_icon_font(mut context: ResMut, state: ResMut) { + let font = egui::FontData::from_static(include_bytes!("../icon.ttf")); + let font_name = "blender".to_string(); + let mut fonts = egui::FontDefinitions::default(); + fonts.font_data.insert(font_name.clone(), font); + fonts + .families + .insert(FontFamily::Name(font_name.clone().into()), vec!["Hack".into(), font_name.clone()]); + fonts.families.get_mut(&FontFamily::Proportional).unwrap().push(font_name.clone()); + fonts.families.get_mut(&FontFamily::Monospace).unwrap().push(font_name); + context.ctx_mut().set_fonts(fonts); + + let mut style = (*context.ctx_mut().style()).clone(); + style.text_styles.insert(egui::TextStyle::Body, FontId { + size: (state.ui_font.size * 0.75).floor(), + family: state.ui_font.family.clone(), + }); + style.text_styles.insert(egui::TextStyle::Body, state.ui_font.clone()); + style.text_styles.insert(egui::TextStyle::Button, state.ui_font.clone()); + style.text_styles.insert(egui::TextStyle::Heading, FontId { + size: (state.ui_font.size * 1.5).floor(), + family: state.ui_font.family.clone(), + }); + style.text_styles.insert(egui::TextStyle::Monospace, state.code_font.clone()); + context.ctx_mut().set_style(style); +} diff --git a/retrotool-gui/src/tabs/mod.rs b/retrotool-gui/src/tabs/mod.rs new file mode 100644 index 0000000..c19a8a4 --- /dev/null +++ b/retrotool-gui/src/tabs/mod.rs @@ -0,0 +1,109 @@ +pub mod model; +pub mod project; +pub mod texture; + +use bevy::{ecs::system::*, prelude::*, render::camera::*}; +use bevy_egui::EguiContext; + +use crate::AssetRef; + +pub enum TabType { + Project(project::ProjectTab), + Texture(texture::TextureTab), + Model(model::ModelTab), + Empty, +} + +pub struct TabState { + pub open_assets: Vec, + pub open_tab: Option, + pub viewport: Viewport, + pub render_layer: u8, +} + +pub trait SystemTab { + type LoadParam: SystemParam; + type UiParam: SystemParam; + + fn load(&mut self, _ctx: &mut EguiContext, _query: SystemParamItem<'_, '_, Self::LoadParam>) {} + + fn close(&mut self, _query: SystemParamItem<'_, '_, Self::LoadParam>) {} // , _ctx: &mut EguiContext + + fn ui( + &mut self, + ui: &mut egui::Ui, + query: SystemParamItem<'_, '_, Self::UiParam>, + state: &mut TabState, + ); + + fn title(&mut self) -> egui::WidgetText; +} + +pub fn load_tab(world: &mut World, ctx: &mut EguiContext, tab: &mut T) { + let mut state: SystemState = SystemState::new(world); + tab.load(ctx, state.get_mut(world)); + state.apply(world); +} + +fn render_tab( + world: &mut World, + ui: &mut egui::Ui, + tab: &mut T, + tab_state: &mut TabState, +) { + let mut state: SystemState = SystemState::new(world); + tab.ui(ui, state.get_mut(world), tab_state); + state.apply(world); +} + +fn close_tab(world: &mut World, tab: &mut T) { + let mut state: SystemState = SystemState::new(world); + tab.close(state.get_mut(world)); + state.apply(world); +} + +pub struct TabViewer<'a> { + pub world: &'a mut World, + pub state: TabState, +} + +impl egui_dock::TabViewer for TabViewer<'_> { + type Tab = TabType; + + fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) { + match tab { + 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::Empty => {} + } + } + + fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText { + match tab { + TabType::Project(tab) => tab.title(), + TabType::Texture(tab) => tab.title(), + TabType::Model(tab) => tab.title(), + TabType::Empty => "".into(), + } + } + + fn on_close(&mut self, tab: &mut Self::Tab) -> bool { + match tab { + TabType::Project(_) => false, + TabType::Texture(tab) => { + close_tab(self.world, tab); + true + } + TabType::Model(tab) => { + close_tab(self.world, tab); + true + } + TabType::Empty => false, + } + } + + fn clear_background(&self, tab: &Self::Tab) -> bool { + !matches!(tab, TabType::Empty | TabType::Model(_)) + } +} diff --git a/retrotool-gui/src/tabs/model.rs b/retrotool-gui/src/tabs/model.rs new file mode 100644 index 0000000..3c7cf64 --- /dev/null +++ b/retrotool-gui/src/tabs/model.rs @@ -0,0 +1,750 @@ +use std::{borrow::Cow, ops::Range}; + +use bevy::{ + asset::LoadState, + core_pipeline::clear_color::ClearColorConfig, + ecs::system::{lifetimeless::*, *}, + prelude::*, + render::{ + camera::Viewport, + mesh::*, + primitives::Aabb, + render_resource::{ + AddressMode, Extent3d, FilterMode, SamplerDescriptor, TextureDescriptor, + TextureDimension, TextureFormat, TextureUsages, + }, + texture::ImageSampler, + view::RenderLayers, + }, + utils::HashMap, +}; +use bevy_egui::EguiContext; +use egui::{Id, PointerButton, Sense}; +use half::f16; +use retrolib::format::{ + cmdl::{ + CMaterialDataInner, EBufferType, EMaterialDataId, EVertexComponent, EVertexDataFormat, + ModelData, STextureUsageInfo, SVertexDataComponent, + }, + txtr::{ETextureFormat, ETextureType}, +}; +use uuid::Uuid; + +use crate::{ + icon, + loaders::{ModelAsset, TextureAsset}, + tabs::SystemTab, + AssetRef, TabState, +}; + +pub struct LoadedModel { + pub entities: Vec<(Entity, bool)>, + pub camera_xf: Transform, + pub upside_down: bool, + pub radius: f32, + pub origin: Vec3, + pub projection: Projection, +} + +pub struct ModelTab { + pub asset_ref: AssetRef, + pub handle: Handle, + pub loaded: Option, +} + +#[derive(Debug, Clone, Default)] +struct VertexBufferInfo { + // pub vertex_count: u32, + pub attributes: Vec<(MeshVertexAttribute, VertexAttributeValues)>, +} + +#[inline] +fn copy_direct(input: &[u8], component: &SVertexDataComponent) -> Vec +where T: bytemuck::AnyBitPattern { + let stride = component.stride as usize; + let mut out = bytemuck::zeroed_vec(input.len() / stride); + let mut offset = component.offset as usize; + for v in &mut out { + *v = *bytemuck::from_bytes(&input[offset..offset + std::mem::size_of::()]); + offset += stride; + } + out +} + +#[inline] +fn copy_converting(input: &[u8], component: &SVertexDataComponent, convert: C) -> Vec +where + T: bytemuck::AnyBitPattern, + R: bytemuck::AnyBitPattern, + C: Fn(T) -> R, +{ + let stride = component.stride as usize; + let mut out = bytemuck::zeroed_vec(input.len() / stride); + let mut offset = component.offset as usize; + for v in &mut out { + *v = convert(*bytemuck::from_bytes(&input[offset..offset + std::mem::size_of::()])); + offset += stride; + } + out +} + +fn convert_component( + input: &[u8], + component: &SVertexDataComponent, +) -> Option<(MeshVertexAttribute, VertexAttributeValues)> { + use EVertexComponent::*; + use EVertexDataFormat::*; + use VertexAttributeValues::*; + let attribute = match component.component { + Position => Mesh::ATTRIBUTE_POSITION, + Normal => Mesh::ATTRIBUTE_NORMAL, + Tangent0 => Mesh::ATTRIBUTE_TANGENT, + TexCoord0 => Mesh::ATTRIBUTE_UV_0, + // Color => Mesh::ATTRIBUTE_COLOR, + // BoneIndices => Mesh::ATTRIBUTE_JOINT_INDEX, + // BoneWeights => Mesh::ATTRIBUTE_JOINT_WEIGHT, + _ => return None, + }; + let values = match component.format { + Rg8Unorm => Unorm8x2(copy_direct(input, component)), + Rg8Uint => Uint8x2(copy_direct(input, component)), + Rg8Snorm => Snorm8x2(copy_direct(input, component)), + Rg8Sint => Sint8x2(copy_direct(input, component)), + R32Uint => Uint32(copy_direct(input, component)), + R32Sint => Sint32(copy_direct(input, component)), + R32Float => Float32(copy_direct(input, component)), + Rg16Unorm => Unorm16x2(copy_direct(input, component)), + Rg16Uint => Uint16x2(copy_direct(input, component)), + Rg16Snorm => Snorm16x2(copy_direct(input, component)), + Rg16Sint => Sint16x2(copy_direct(input, component)), + Rg16Float => Float32x2(copy_converting(input, component, |v: [u16; 2]| { + v.map(|u| f16::from_bits(u).to_f32()) + })), + Rgba8Unorm => match component.component { + Color => Float32x4(copy_converting(input, component, |v: [u8; 4]| { + v.map(|u| u as f32 * 255.0) + })), + _ => Unorm8x4(copy_direct(input, component)), + }, + Rgba8Uint => Uint8x4(copy_direct(input, component)), + Rgba8Snorm => Snorm8x4(copy_direct(input, component)), + Rgba8Sint => Sint8x4(copy_direct(input, component)), + Rg32Uint => Uint32x2(copy_direct(input, component)), + Rg32Sint => Sint32x2(copy_direct(input, component)), + Rg32Float => Float32x2(copy_direct(input, component)), + Rgba16Unorm => Unorm16x4(copy_direct(input, component)), + Rgba16Uint => Uint16x4(copy_direct(input, component)), + Rgba16Snorm => Snorm16x4(copy_direct(input, component)), + Rgba16Sint => Sint16x4(copy_direct(input, component)), + Rgba16Float => match component.component { + Position | Normal => Float32x3(copy_converting(input, component, |v: [u16; 3]| { + v.map(|u| f16::from_bits(u).to_f32()) + })), + TexCoord0 => Float32x2(copy_converting(input, component, |v: [u16; 2]| { + v.map(|u| f16::from_bits(u).to_f32()) + })), + _ => Float32x4(copy_converting(input, component, |v: [u16; 4]| { + v.map(|u| f16::from_bits(u).to_f32()) + })), + }, + Rgb32Uint => Uint32x3(copy_direct(input, component)), + Rgb32Sint => Sint32x3(copy_direct(input, component)), + Rgb32Float => Float32x3(copy_direct(input, component)), + Rgba32Uint => Uint32x4(copy_direct(input, component)), + Rgba32Sint => Sint32x4(copy_direct(input, component)), + Rgba32Float => match component.component { + Position | Normal => Float32x3(copy_direct(input, component)), + TexCoord0 => Float32x2(copy_direct(input, component)), + _ => Float32x4(copy_direct(input, component)), + }, + R16Uint => Uint32(copy_converting(input, component, |v: u16| v as u32)), + R16Sint => Sint32(copy_converting(input, component, |v: i16| v as i32)), + R16Float => Float32(copy_converting(input, component, |v: u16| f16::from_bits(v).to_f32())), + _ => todo!(), + }; + Some((attribute, values)) +} + +fn slice_vertices(values: &VertexAttributeValues, range: Range) -> VertexAttributeValues { + use VertexAttributeValues::*; + match values { + Float32(vec) => Float32(vec[range].to_vec()), + Sint32(vec) => Sint32(vec[range].to_vec()), + Uint32(vec) => Uint32(vec[range].to_vec()), + Float32x2(vec) => Float32x2(vec[range].to_vec()), + Sint32x2(vec) => Sint32x2(vec[range].to_vec()), + Uint32x2(vec) => Uint32x2(vec[range].to_vec()), + Float32x3(vec) => Float32x3(vec[range].to_vec()), + Sint32x3(vec) => Sint32x3(vec[range].to_vec()), + Uint32x3(vec) => Uint32x3(vec[range].to_vec()), + Float32x4(vec) => Float32x4(vec[range].to_vec()), + Sint32x4(vec) => Sint32x4(vec[range].to_vec()), + Uint32x4(vec) => Uint32x4(vec[range].to_vec()), + Sint16x2(vec) => Sint16x2(vec[range].to_vec()), + Snorm16x2(vec) => Snorm16x2(vec[range].to_vec()), + Uint16x2(vec) => Uint16x2(vec[range].to_vec()), + Unorm16x2(vec) => Unorm16x2(vec[range].to_vec()), + Sint16x4(vec) => Sint16x4(vec[range].to_vec()), + Snorm16x4(vec) => Snorm16x4(vec[range].to_vec()), + Uint16x4(vec) => Uint16x4(vec[range].to_vec()), + Unorm16x4(vec) => Unorm16x4(vec[range].to_vec()), + Sint8x2(vec) => Sint8x2(vec[range].to_vec()), + Snorm8x2(vec) => Snorm8x2(vec[range].to_vec()), + Uint8x2(vec) => Uint8x2(vec[range].to_vec()), + Unorm8x2(vec) => Unorm8x2(vec[range].to_vec()), + Sint8x4(vec) => Sint8x4(vec[range].to_vec()), + Snorm8x4(vec) => Snorm8x4(vec[range].to_vec()), + Uint8x4(vec) => Uint8x4(vec[range].to_vec()), + Unorm8x4(vec) => Unorm8x4(vec[range].to_vec()), + } +} + +#[inline] +fn slice_indices(input: &[T], start: u32, count: u32) -> (Vec, Range) +where T: num_traits::Num + num_traits::Bounded + Copy + PartialOrd + TryInto { + let slice = &input[start as usize..(start + count) as usize]; + let mut min = T::max_value(); + let mut max = T::min_value(); + for &v in slice { + if v < min { + min = v; + } + if v > max { + max = v; + } + } + let values = + if min == T::zero() { slice.to_vec() } else { slice.iter().map(|&v| v - min).collect() }; + (values, min.try_into().unwrap_or(usize::MAX)..max.try_into().unwrap_or(usize::MAX) + 1) +} + +#[derive(Clone)] +enum IndicesSlice<'a> { + U16(Cow<'a, [u16]>), + U32(Cow<'a, [u32]>), +} + +#[derive(Component)] +pub struct TemporaryTag; + +fn sampler_descriptor_from_usage<'a>(usage: &STextureUsageInfo) -> SamplerDescriptor<'a> { + SamplerDescriptor { + label: None, + address_mode_u: match usage.wrap_x { + 0 | u32::MAX => AddressMode::ClampToEdge, + 1 => AddressMode::Repeat, + 2 => AddressMode::MirrorRepeat, + 3 => todo!("Mirror clamp"), + 4 => AddressMode::ClampToBorder, + 5 => todo!("Clamp"), + n => todo!("wrap {n}"), + }, + address_mode_v: match usage.wrap_y { + 0 | u32::MAX => AddressMode::ClampToEdge, + 1 => AddressMode::Repeat, + 2 => AddressMode::MirrorRepeat, + 3 => todo!("Mirror clamp"), + 4 => AddressMode::ClampToBorder, + 5 => todo!("Clamp"), + n => todo!("wrap {n}"), + }, + address_mode_w: match usage.wrap_z { + 0 | u32::MAX => AddressMode::ClampToEdge, + 1 => AddressMode::Repeat, + 2 => AddressMode::MirrorRepeat, + 3 => todo!("Mirror clamp"), + 4 => AddressMode::ClampToBorder, + 5 => todo!("Clamp"), + n => todo!("wrap {n}"), + }, + mag_filter: match usage.filter { + 0 | u32::MAX => FilterMode::Nearest, + 1 => FilterMode::Linear, + n => todo!("Filter {n}"), + }, + min_filter: match usage.filter { + 0 | u32::MAX => FilterMode::Nearest, + 1 => FilterMode::Linear, + n => todo!("Filter {n}"), + }, + mipmap_filter: FilterMode::Linear, // TODO? + lod_min_clamp: 0.0, + lod_max_clamp: 0.0, + compare: None, + anisotropy_clamp: None, + border_color: None, + } +} + +impl SystemTab for ModelTab { + type LoadParam = ( + SCommands, + SResMut>, + SResMut>, + SResMut>, + SResMut>, + SResMut>, + SResMut, + ); + type UiParam = SCommands; + + 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, + ) = query; + if let Some(loaded) = &self.loaded { + for (entity, _) in &loaded.entities { + if let Some(mut commands) = commands.get_entity(*entity) { + commands.insert(Visibility::INVISIBLE); + } + } + return; + } + + let ModelAsset { + inner: ModelData { head, mtrl, mesh, vbuf, ibuf, vtx_buffers, idx_buffers }, + textures, + } = match models.get_mut(&self.handle) { + Some(v) => v, + None => return, + }; + // Ensure all dependencies loaded + match server.get_group_load_state(textures.iter().map(|(_, h)| h.id())) { + LoadState::Loaded => {} + _ => return, + } + + // Build sampler descriptors + let mut sampler_descriptors = HashMap::::new(); + for mat in &mtrl.materials { + for data in &mat.data { + match &data.data { + CMaterialDataInner::Texture(texture) => { + if let Some(usage) = &texture.usage { + sampler_descriptors + .insert(texture.id, sampler_descriptor_from_usage(usage)); + } + } + CMaterialDataInner::LayeredTexture(textures) => { + for texture in &textures.textures { + if let Some(usage) = &texture.usage { + sampler_descriptors + .insert(texture.id, sampler_descriptor_from_usage(usage)); + } + } + } + _ => continue, + } + } + } + + // Build texture images + let mut texture_handles = HashMap::>::new(); + for (id, handle) in textures { + let txtr = texture_assets.get(handle).unwrap(); + let image_handle = images.add(Image { + data: txtr.rgba.as_ref().unwrap_or(&txtr.inner.data).clone(), + texture_descriptor: TextureDescriptor { + label: None, + size: Extent3d { + width: txtr.inner.head.width, + height: txtr.inner.head.height, + depth_or_array_layers: if txtr.rgba.is_some() { + 1 // FIXME + } else { + txtr.inner.head.layers + }, + }, + mip_level_count: if txtr.rgba.is_some() { + 1 + } else { + txtr.inner.head.mip_sizes.len() as u32 + }, + sample_count: 1, + dimension: match txtr.inner.head.kind { + ETextureType::_1D => TextureDimension::D1, + ETextureType::_2D => TextureDimension::D2, + ETextureType::_3D => TextureDimension::D3, + ETextureType::Cube => TextureDimension::D2, + ETextureType::_1DArray => TextureDimension::D1, + ETextureType::_2DArray => TextureDimension::D2, + ETextureType::_2DMultisample => TextureDimension::D2, + ETextureType::_2DMultisampleArray => TextureDimension::D2, + ETextureType::CubeArray => TextureDimension::D2, + }, + format: if txtr.rgba.is_some() { + if txtr.inner.head.format.is_srgb() { + TextureFormat::Rgba8UnormSrgb + } else { + TextureFormat::Rgba8Unorm + } + } else { + match txtr.inner.head.format { + ETextureFormat::R8Unorm => TextureFormat::Rgba8Unorm, + ETextureFormat::R8Snorm => TextureFormat::R8Snorm, + ETextureFormat::R8Uint => TextureFormat::R8Uint, + ETextureFormat::R8Sint => TextureFormat::R8Sint, + ETextureFormat::R16Unorm => TextureFormat::R16Unorm, + ETextureFormat::R16Snorm => TextureFormat::R16Snorm, + ETextureFormat::R16Uint => TextureFormat::R16Uint, + ETextureFormat::R16Sint => TextureFormat::R16Sint, + ETextureFormat::R16Float => TextureFormat::R16Float, + ETextureFormat::R32Uint => TextureFormat::R32Uint, + ETextureFormat::R32Sint => TextureFormat::R32Sint, + ETextureFormat::Rgba8Unorm => TextureFormat::Rgba8Unorm, + ETextureFormat::Rgba8Srgb => TextureFormat::Rgba8UnormSrgb, + ETextureFormat::Rgba16Float => TextureFormat::Rgba16Float, + ETextureFormat::Rgba32Float => TextureFormat::Rgba32Float, + ETextureFormat::Depth16Unorm => TextureFormat::Depth16Unorm, + ETextureFormat::Depth16Unorm2 => TextureFormat::Depth16Unorm, + ETextureFormat::Depth24S8Unorm => TextureFormat::Depth24PlusStencil8, + ETextureFormat::Depth32Float => TextureFormat::Depth32Float, + ETextureFormat::RgbaBc1Unorm => TextureFormat::Bc1RgbaUnorm, + ETextureFormat::RgbaBc1Srgb => TextureFormat::Bc1RgbaUnormSrgb, + ETextureFormat::RgbaBc2Unorm => TextureFormat::Bc2RgbaUnorm, + ETextureFormat::RgbaBc2Srgb => TextureFormat::Bc2RgbaUnormSrgb, + ETextureFormat::RgbaBc3Unorm => TextureFormat::Bc3RgbaUnorm, + ETextureFormat::RgbaBc3Srgb => TextureFormat::Bc3RgbaUnormSrgb, + ETextureFormat::RgbaBc4Unorm => TextureFormat::Bc4RUnorm, + ETextureFormat::RgbaBc4Snorm => TextureFormat::Bc4RSnorm, + ETextureFormat::RgbaBc5Unorm => TextureFormat::Bc5RgUnorm, + ETextureFormat::RgbaBc5Snorm => TextureFormat::Bc5RgSnorm, + ETextureFormat::Rg11B10Float => TextureFormat::Rg11b10Float, + ETextureFormat::R32Float => TextureFormat::R32Float, + ETextureFormat::Rg8Unorm => TextureFormat::Rg8Unorm, + ETextureFormat::Rg8Snorm => TextureFormat::Rg8Snorm, + ETextureFormat::Rg8Uint => TextureFormat::Rg8Uint, + ETextureFormat::Rg8Sint => TextureFormat::Rg8Sint, + ETextureFormat::Rg16Float => TextureFormat::Rg16Float, + ETextureFormat::Rg16Unorm => TextureFormat::Rg16Unorm, + ETextureFormat::Rg16Snorm => TextureFormat::Rg16Snorm, + ETextureFormat::Rg16Uint => TextureFormat::Rg16Uint, + ETextureFormat::Rg16Sint => TextureFormat::Rg16Sint, + ETextureFormat::Rgb10A2Unorm => TextureFormat::Rgb10a2Unorm, + ETextureFormat::Rg32Uint => TextureFormat::Rg32Uint, + ETextureFormat::Rg32Sint => TextureFormat::Rg32Sint, + ETextureFormat::Rg32Float => TextureFormat::Rg32Float, + ETextureFormat::Rgba16Unorm => TextureFormat::Rgba16Unorm, + ETextureFormat::Rgba16Snorm => TextureFormat::Rgba16Snorm, + ETextureFormat::Rgba16Uint => TextureFormat::Rgba16Uint, + ETextureFormat::Rgba16Sint => TextureFormat::Rgba16Sint, + ETextureFormat::Rgba32Uint => TextureFormat::Rgba32Uint, + ETextureFormat::Rgba32Sint => TextureFormat::Rgba32Sint, + ETextureFormat::BptcUfloat => TextureFormat::Bc6hRgbUfloat, + ETextureFormat::BptcSfloat => TextureFormat::Bc6hRgbSfloat, + ETextureFormat::BptcUnorm => TextureFormat::Bc7RgbaUnorm, + ETextureFormat::BptcUnormSrgb => TextureFormat::Bc7RgbaUnormSrgb, + format => todo!("format {format:?}"), + } + }, + usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, + }, + sampler_descriptor: sampler_descriptors + .get(id) + .map(|desc| ImageSampler::Descriptor(desc.clone())) + .unwrap_or_default(), + texture_view_descriptor: None, + }); + texture_handles.insert(*id, image_handle); + } + + // Build vertex buffers + let mut buf_infos: Vec = Vec::with_capacity(vtx_buffers.len()); + let mut cur_buf = 0usize; + for info in &vbuf.info { + let num_buffers = info.unk as usize; // guess + let mut attributes = Vec::with_capacity(info.components.len()); + for component in &info.components { + let input = &*vtx_buffers[cur_buf + component.buffer_index as usize]; + if let Some((attribute, values)) = convert_component(input, component) { + attributes.push((attribute, values)); + } + } + buf_infos.push(VertexBufferInfo { + // vertex_count: info.vertex_count, + attributes, + }); + cur_buf += num_buffers; + } + + // Process index buffers + let mut index_buffers = Vec::::new(); + for (idx, &index_type) in ibuf.info.iter().enumerate() { + let in_buf = &*idx_buffers[idx]; + let out = match index_type { + EBufferType::U8 => { + IndicesSlice::U16(Cow::Owned(in_buf.iter().map(|&u| u as u16).collect())) + } + EBufferType::U16 => IndicesSlice::U16(Cow::Borrowed(bytemuck::cast_slice(in_buf))), + EBufferType::U32 => IndicesSlice::U32(Cow::Borrowed(bytemuck::cast_slice(in_buf))), + }; + index_buffers.push(out); + } + + // Build materials + let mut material_handles = Vec::with_capacity(mtrl.materials.len()); + for mat in &mtrl.materials { + let mut out_mat = StandardMaterial::default(); + println!("Shader {}, unk {}", mat.shader_id, mat.unk_guid); + let _ = server.load_untyped(format!("{}.MTRL", mat.shader_id)); + for data in &mat.data { + match data.data_id { + EMaterialDataId::DIFT | EMaterialDataId::BCLR => match &data.data { + CMaterialDataInner::Texture(texture) => { + out_mat.base_color_texture = texture_handles.get(&texture.id).cloned(); + } + _ => { + log::warn!( + "Unsupported material data type for DIFT {:?}", + data.data_type + ); + } + }, + EMaterialDataId::DIFC => match &data.data { + CMaterialDataInner::Color(color) => { + out_mat.base_color = Color::rgba(color.r, color.g, color.b, color.a); + } + _ => log::warn!( + "Unsupported material data type for DIFC {:?}", + data.data_type + ), + }, + EMaterialDataId::ICAN => match &data.data { + CMaterialDataInner::Texture(_texture) => { + // out_mat.emissive_texture = texture_handles.get(&texture.id).cloned(); + } + _ => log::warn!( + "Unsupported material data type for ICAN {:?}", + data.data_type + ), + }, + EMaterialDataId::ICNC => match &data.data { + CMaterialDataInner::Color(_color) => { + // out_mat.emissive = Color::rgba(color.r, color.g, color.b, color.a); + } + _ => log::warn!( + "Unsupported material data type for ICNC {:?}", + data.data_type + ), + }, + id => { + log::warn!("Unsupported material data ID {id:?}"); + } + } + } + material_handles.push(materials.add(out_mat)); + } + + // Process meshes + let aabb = Aabb::from_min_max( + Vec3::new(head.bounds.min.x, head.bounds.min.y, head.bounds.min.z), + Vec3::new(head.bounds.max.x, head.bounds.max.y, head.bounds.max.z), + ); + let mut entities = vec![]; + for (_idx, in_mesh) in mesh.meshes.iter().enumerate() { + let (indices, vert_range) = match &index_buffers[in_mesh.idx_buf_idx as usize] { + IndicesSlice::U16(indices) => { + let (values, range) = + slice_indices(indices, in_mesh.index_start, in_mesh.index_count); + (Indices::U16(values), range) + } + IndicesSlice::U32(indices) => { + let (values, range) = + slice_indices(indices, in_mesh.index_start, in_mesh.index_count); + (Indices::U32(values), range) + } + }; + let mut out_mesh = Mesh::new(PrimitiveTopology::TriangleList); + out_mesh.set_indices(Some(indices)); + for (component, values) in &buf_infos[in_mesh.vtx_buf_idx as usize].attributes { + out_mesh.insert_attribute( + component.clone(), + slice_vertices(values, vert_range.clone()), + ); + } + entities.push(( + commands + .spawn(PbrBundle { + mesh: meshes.add(out_mesh), + material: material_handles[in_mesh.material_idx as usize].clone(), + transform: Transform::from_translation((-aabb.center).into()), + ..default() + }) + .id(), + true, + )); + } + + let radius = (aabb.max() - aabb.min()).max_element() * 1.25; + let mut camera_xf = + Transform::from_xyz(-radius, 5.0, radius).looking_at(Vec3::ZERO, Vec3::Y); + let rot_matrix = Mat3::from_quat(camera_xf.rotation); + camera_xf.translation = rot_matrix.mul_vec3(Vec3::new(0.0, 0.0, radius)); + self.loaded = Some(LoadedModel { + entities, + camera_xf, + upside_down: false, + radius, + origin: Vec3::ZERO, + projection: Projection::Perspective(default()), + }); + } + + fn close(&mut self, query: SystemParamItem<'_, '_, Self::LoadParam>) { + let (mut commands, _, _, _, _, _, _) = query; + if let Some(loaded) = &self.loaded { + for (entity, _) in &loaded.entities { + if let Some(mut commands) = commands.get_entity(*entity) { + commands.despawn(); + } + } + } + } + + fn ui( + &mut self, + ui: &mut egui::Ui, + query: SystemParamItem<'_, '_, Self::UiParam>, + state: &mut TabState, + ) { + let rect = ui.available_rect_before_wrap(); + let left_top = rect.left_top(); + let size = rect.size(); + 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, Id::new("background"), Sense::click_and_drag()); + + let mut commands = query; + if let Some(loaded) = &mut self.loaded { + let mut transform = &mut loaded.camera_xf; + 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(delta.x, 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 = transform.rotation * Vec3::Y; + loaded.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 loaded.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); + transform.rotation = yaw * transform.rotation; // rotate around global y axis + transform.rotation *= pitch; // rotate around local x axis + } else if pan.length_squared() > 0.0 { + any = true; + if let Projection::Perspective(projection) = &loaded.projection { + pan *= Vec2::new(projection.fov * projection.aspect_ratio, projection.fov) + / Vec2::new(rect.width(), rect.height()); + } + // translate by local axes + let right = transform.rotation * Vec3::X * -pan.x; + let up = transform.rotation * Vec3::Y * pan.y; + // make panning proportional to distance away from focus point + let translation = (right + up) * loaded.radius; + loaded.origin += translation; + } else if scroll.y.abs() > 0.0 { + any = true; + loaded.radius -= (scroll.y / 50.0/* TODO ? */) * loaded.radius * 0.2; + // dont allow zoom to reach zero or you get stuck + loaded.radius = f32::max(loaded.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(transform.rotation); + transform.translation = + loaded.origin + rot_matrix.mul_vec3(Vec3::new(0.0, 0.0, loaded.radius)); + } + + commands.spawn(( + Camera3dBundle { + camera_3d: Camera3d { + clear_color: if state.render_layer == 0 { + ClearColorConfig::Default + } else { + ClearColorConfig::None + }, + ..default() + }, + camera: Camera { + viewport: Some(viewport), + priority: state.render_layer as isize, + ..default() + }, + transform: loaded.camera_xf, + ..default() + }, + RenderLayers::layer(state.render_layer), + TemporaryTag, + )); + 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), + TemporaryTag, + )); + + egui::Frame::group(ui.style()).show(ui, |ui| { + egui::ScrollArea::vertical().max_height(rect.height() * 0.25).show(ui, |ui| { + for (idx, (entity, visible)) in loaded.entities.iter_mut().enumerate() { + ui.checkbox(visible, format!("Mesh {idx}")); + if let Some(mut commands) = commands.get_entity(*entity) { + commands.insert(( + if *visible { Visibility::VISIBLE } else { Visibility::INVISIBLE }, + RenderLayers::layer(state.render_layer), + )); + } + } + }); + }); + state.render_layer += 1; + } + } + + fn title(&mut self) -> egui::WidgetText { + format!("{} {} {}", icon::FILE_3D, self.asset_ref.kind, self.asset_ref.id).into() + } +} diff --git a/retrotool-gui/src/tabs/project.rs b/retrotool-gui/src/tabs/project.rs new file mode 100644 index 0000000..24fe95b --- /dev/null +++ b/retrotool-gui/src/tabs/project.rs @@ -0,0 +1,98 @@ +use bevy::{ + ecs::system::{lifetimeless::*, *}, + prelude::*, +}; +use egui::{text::LayoutJob, Color32, TextFormat, Widget}; +use retrolib::format::{cmdl::K_FORM_CMDL, txtr::K_FORM_TXTR, FourCC}; + +use crate::{ + icon, + loaders::{ModelAsset, PackageDirectory, TextureAsset}, + tabs::{model::ModelTab, texture::TextureTab, SystemTab, TabState, TabType}, + AssetRef, +}; + +pub const K_FORM_FMV0: FourCC = FourCC(*b"FMV0"); +pub const K_FORM_ROOM: FourCC = FourCC(*b"ROOM"); + +pub struct ProjectTab; + +impl SystemTab for ProjectTab { + type LoadParam = (); + type UiParam = (SRes, SRes>); + + fn ui( + &mut self, + ui: &mut egui::Ui, + query: SystemParamItem<'_, '_, Self::UiParam>, + state: &mut TabState, + ) { + let (server, packages) = query; + let mut packages_sorted = + packages.iter().map(|(_, p)| p).collect::>(); + packages_sorted.sort_by_key(|p| &p.name); + for package in packages_sorted { + egui::CollapsingHeader::new(&package.name).show(ui, |ui| { + for entry in &package.entries { + let monospace = + ui.style().text_styles.get(&egui::TextStyle::Monospace).unwrap().clone(); + let mut job = LayoutJob::simple( + format!( + "{} {} {}", + match entry.kind { + K_FORM_TXTR => icon::TEXTURE, + K_FORM_CMDL => icon::FILE_3D, + K_FORM_FMV0 => icon::FILE_MOVIE, + K_FORM_ROOM => icon::SCENE_DATA, + _ => icon::FILE, + }, + entry.kind, + entry.id + ), + monospace.clone(), + Color32::GRAY, + 0.0, + ); + if let Some(name) = &entry.name { + job.append( + &format!("\n{name}"), + 0.0, + TextFormat::simple(monospace, Color32::WHITE), + ); + } + let asset_ref = AssetRef { id: entry.id, kind: entry.kind }; + if egui::SelectableLabel::new(state.open_assets.contains(&asset_ref), job) + .ui(ui) + .clicked() + { + match entry.kind { + K_FORM_TXTR => { + let handle = server.load::(format!( + "{}.{}", + entry.id, entry.kind + )); + state.open_tab = Some(TabType::Texture(TextureTab { + asset_ref: asset_ref.clone(), + handle, + loaded_texture: None, + })); + } + K_FORM_CMDL => { + let handle = server + .load::(format!("{}.{}", entry.id, entry.kind)); + state.open_tab = Some(TabType::Model(ModelTab { + asset_ref: asset_ref.clone(), + handle, + loaded: None, + })); + } + _ => {} + } + } + } + }); + } + } + + fn title(&mut self) -> egui::WidgetText { format!("{} Browser", icon::FILEBROWSER).into() } +} diff --git a/retrotool-gui/src/tabs/texture.rs b/retrotool-gui/src/tabs/texture.rs new file mode 100644 index 0000000..120189b --- /dev/null +++ b/retrotool-gui/src/tabs/texture.rs @@ -0,0 +1,226 @@ +use bevy::{ + asset::LoadState, + ecs::system::{lifetimeless::*, *}, + prelude::*, + render::render_resource::*, +}; +use bevy_egui::EguiContext; +use retrolib::format::txtr::{ETextureFormat, ETextureType}; + +use crate::{loaders::TextureAsset, tabs::SystemTab, AssetRef, TabState, icon}; + +pub struct LoadedTexture { + pub texture_ids: Vec, +} + +pub struct TextureTab { + pub asset_ref: AssetRef, + pub handle: Handle, + pub loaded_texture: Option, +} + +impl SystemTab for TextureTab { + type LoadParam = (SRes>, SResMut>); + type UiParam = (SRes, SRes>); + + fn load(&mut self, ctx: &mut EguiContext, query: SystemParamItem<'_, '_, Self::LoadParam>) { + if self.loaded_texture.is_some() { + return; + } + + let (textures, mut images) = query; + let Some(txtr) = textures.get(&self.handle) else { return; }; + let mut texture_ids = Vec::new(); + if let Some(rgba) = &txtr.rgba { + let image_handle = images.add(Image { + data: rgba.clone(), + texture_descriptor: TextureDescriptor { + label: None, + size: Extent3d { + width: txtr.inner.head.width, + height: txtr.inner.head.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: TextureDimension::D2, + format: if txtr.inner.head.format.is_srgb() { + TextureFormat::Rgba8UnormSrgb + } else { + TextureFormat::Rgba8Unorm + }, + usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, + }, + sampler_descriptor: default(), + texture_view_descriptor: None, + }); + texture_ids.push(ctx.add_image(image_handle)); + } else { + let array_stride: usize = + (txtr.inner.head.mip_sizes.iter().sum::() / txtr.inner.head.layers) as usize; + for layer in 0..txtr.inner.head.layers as usize { + let image_handle = images.add(Image { + data: txtr.inner.data + [layer * array_stride..(layer * array_stride) + array_stride] + .to_vec(), + texture_descriptor: TextureDescriptor { + label: None, + size: Extent3d { + width: txtr.inner.head.width, + height: txtr.inner.head.height, + depth_or_array_layers: 1, + }, + mip_level_count: txtr.inner.head.mip_sizes.len() as u32, + sample_count: 1, + dimension: match txtr.inner.head.kind { + ETextureType::_1D => TextureDimension::D1, + ETextureType::_2D => TextureDimension::D2, + ETextureType::_3D => TextureDimension::D3, + ETextureType::Cube => TextureDimension::D2, + ETextureType::_1DArray => TextureDimension::D1, + ETextureType::_2DArray => TextureDimension::D2, + ETextureType::_2DMultisample => TextureDimension::D2, + ETextureType::_2DMultisampleArray => TextureDimension::D2, + ETextureType::CubeArray => TextureDimension::D2, + }, + format: match txtr.inner.head.format { + ETextureFormat::R8Unorm => TextureFormat::Rgba8Unorm, + ETextureFormat::R8Snorm => TextureFormat::R8Snorm, + ETextureFormat::R8Uint => TextureFormat::R8Uint, + ETextureFormat::R8Sint => TextureFormat::R8Sint, + ETextureFormat::R16Unorm => TextureFormat::R16Unorm, + ETextureFormat::R16Snorm => TextureFormat::R16Snorm, + ETextureFormat::R16Uint => TextureFormat::R16Uint, + ETextureFormat::R16Sint => TextureFormat::R16Sint, + ETextureFormat::R16Float => TextureFormat::R16Float, + ETextureFormat::R32Uint => TextureFormat::R32Uint, + ETextureFormat::R32Sint => TextureFormat::R32Sint, + ETextureFormat::Rgba8Unorm => TextureFormat::Rgba8Unorm, + ETextureFormat::Rgba8Srgb => TextureFormat::Rgba8UnormSrgb, + ETextureFormat::Rgba16Float => TextureFormat::Rgba16Float, + ETextureFormat::Rgba32Float => TextureFormat::Rgba32Float, + ETextureFormat::Depth16Unorm => TextureFormat::Depth16Unorm, + ETextureFormat::Depth16Unorm2 => TextureFormat::Depth16Unorm, + ETextureFormat::Depth24S8Unorm => TextureFormat::Depth24PlusStencil8, + ETextureFormat::Depth32Float => TextureFormat::Depth32Float, + ETextureFormat::RgbaBc1Unorm => TextureFormat::Bc1RgbaUnorm, + ETextureFormat::RgbaBc1Srgb => TextureFormat::Bc1RgbaUnormSrgb, + ETextureFormat::RgbaBc2Unorm => TextureFormat::Bc2RgbaUnorm, + ETextureFormat::RgbaBc2Srgb => TextureFormat::Bc2RgbaUnormSrgb, + ETextureFormat::RgbaBc3Unorm => TextureFormat::Bc3RgbaUnorm, + ETextureFormat::RgbaBc3Srgb => TextureFormat::Bc3RgbaUnormSrgb, + ETextureFormat::RgbaBc4Unorm => TextureFormat::Bc4RUnorm, + ETextureFormat::RgbaBc4Snorm => TextureFormat::Bc4RSnorm, + ETextureFormat::RgbaBc5Unorm => TextureFormat::Bc5RgUnorm, + ETextureFormat::RgbaBc5Snorm => TextureFormat::Bc5RgSnorm, + ETextureFormat::Rg11B10Float => TextureFormat::Rg11b10Float, + ETextureFormat::R32Float => TextureFormat::R32Float, + ETextureFormat::Rg8Unorm => TextureFormat::Rg8Unorm, + ETextureFormat::Rg8Snorm => TextureFormat::Rg8Snorm, + ETextureFormat::Rg8Uint => TextureFormat::Rg8Uint, + ETextureFormat::Rg8Sint => TextureFormat::Rg8Sint, + ETextureFormat::Rg16Float => TextureFormat::Rg16Float, + ETextureFormat::Rg16Unorm => TextureFormat::Rg16Unorm, + ETextureFormat::Rg16Snorm => TextureFormat::Rg16Snorm, + ETextureFormat::Rg16Uint => TextureFormat::Rg16Uint, + ETextureFormat::Rg16Sint => TextureFormat::Rg16Sint, + ETextureFormat::Rgb10A2Unorm => TextureFormat::Rgb10a2Unorm, + ETextureFormat::Rg32Uint => TextureFormat::Rg32Uint, + ETextureFormat::Rg32Sint => TextureFormat::Rg32Sint, + ETextureFormat::Rg32Float => TextureFormat::Rg32Float, + ETextureFormat::Rgba16Unorm => TextureFormat::Rgba16Unorm, + ETextureFormat::Rgba16Snorm => TextureFormat::Rgba16Snorm, + ETextureFormat::Rgba16Uint => TextureFormat::Rgba16Uint, + ETextureFormat::Rgba16Sint => TextureFormat::Rgba16Sint, + ETextureFormat::Rgba32Uint => TextureFormat::Rgba32Uint, + ETextureFormat::Rgba32Sint => TextureFormat::Rgba32Sint, + ETextureFormat::BptcUfloat => TextureFormat::Bc6hRgbUfloat, + ETextureFormat::BptcSfloat => TextureFormat::Bc6hRgbSfloat, + ETextureFormat::BptcUnorm => TextureFormat::Bc7RgbaUnorm, + ETextureFormat::BptcUnormSrgb => TextureFormat::Bc7RgbaUnormSrgb, + _ => todo!(), + }, + usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, + }, + sampler_descriptor: default(), + texture_view_descriptor: None, + }); + texture_ids.push(ctx.add_image(image_handle)); + } + }; + self.loaded_texture = Some(LoadedTexture { texture_ids }); + } + + fn ui( + &mut self, + ui: &mut egui::Ui, + query: SystemParamItem<'_, '_, Self::UiParam>, + _state: &mut TabState, + ) { + let (server, textures) = 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 loaded = self.loaded_texture.as_mut().unwrap(); + if let Some(txtr) = textures.get(&self.handle) { + ui.label(format!("Type: {:?}", txtr.inner.head.kind)); + ui.label(format!("Format: {:?}", txtr.inner.head.format)); + ui.label(format!( + "Dimensions: {}x{}x{} (mips: {})", + txtr.inner.head.width, + txtr.inner.head.height, + txtr.inner.head.layers, + txtr.inner.head.mip_sizes.len() + )); + if txtr.inner.head.kind == ETextureType::Cube && loaded.texture_ids.len() == 6 { + let width = txtr.inner.head.width; + let height = txtr.inner.head.height; + let (_, rect) = + ui.allocate_space(egui::Vec2 { x: (width * 4) as f32, y: (height * 3) as f32 }); + let size = egui::Vec2 { x: width as f32, y: height as f32 }; + let mut draw_image = |i: usize, x: u32, y: u32| { + let min = egui::Vec2 { x: (width * x) as f32, y: (height * y) as f32 }; + let max = + egui::Vec2 { x: (width * (x + 1)) as f32, y: (height * (y + 1)) as f32 }; + egui::widgets::Image::new(loaded.texture_ids[i], size) + .paint_at(ui, egui::Rect { min: rect.min + min, max: rect.min + max }); + }; + draw_image(2, 1, 0); + draw_image(1, 0, 1); + draw_image(4, 1, 1); + draw_image(0, 2, 1); + draw_image(5, 3, 1); + draw_image(3, 1, 2); + } else { + for image in &loaded.texture_ids { + ui.add(egui::widgets::Image::new(*image, egui::Vec2 { + x: txtr.inner.head.width as f32, + y: txtr.inner.head.height as f32, + })); + } + } + } + } + + fn title(&mut self) -> egui::WidgetText { + format!("{} {} {}", icon::TEXTURE, self.asset_ref.kind, self.asset_ref.id).into() + } +} diff --git a/retrotool/Cargo.toml b/retrotool/Cargo.toml index 867018c..d108a57 100644 --- a/retrotool/Cargo.toml +++ b/retrotool/Cargo.toml @@ -11,19 +11,17 @@ readme = "README.md" categories = ["command-line-utilities"] [dependencies] -retrolib = { path = "../lib" } anyhow = "1.0.69" argh = "0.1.10" -# astc-decode = "0.3.1" binrw = "0.11.1" binrw_derive = "0.11.1" ddsfile = { git = "https://github.com/encounter/ddsfile", rev = "880f04c1dffa680eab0e9e09cfa58591fe186a31" } env_logger = "0.10.0" gltf-json = { version = "1.1.0", features = ["names", "extras"] } half = "2.2.1" -# image = "0.24.5" log = "0.4.17" memmap2 = "0.5.8" +retrolib = { path = "../lib" } serde_json = "1.0.93" tegra_swizzle = "0.3.0" uuid = "1.3.0" diff --git a/retrotool/src/cmd/cmdl.rs b/retrotool/src/cmd/cmdl.rs index e161066..94ed7ef 100644 --- a/retrotool/src/cmd/cmdl.rs +++ b/retrotool/src/cmd/cmdl.rs @@ -1,5 +1,4 @@ use std::{ - borrow::Cow, collections::HashMap, fs, fs::DirBuilder, @@ -175,7 +174,7 @@ fn convert(args: ConvertArgs) -> Result<()> { continue; } - let mut reader = Cursor::new(buf.as_ref()); + let mut reader = Cursor::new(&*buf); let mut new_buf: Vec = Vec::with_capacity(info.vertex_count as usize * info.out_stride as usize); let mut tmp_buf = vec![0u8; 16]; // max size of attribute @@ -209,7 +208,7 @@ fn convert(args: ConvertArgs) -> Result<()> { } } } - *buf = Cow::Owned(new_buf); + *buf = new_buf; } DirBuilder::new().recursive(true).create(&args.out_dir)?;