diff options
author | metamuffin <metamuffin@disroot.org> | 2025-03-12 17:54:43 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2025-03-12 17:54:43 +0100 |
commit | dc8304afefa71037bea99722bee29f7645753836 (patch) | |
tree | 4fd2b70d6faec18573bd590442b443b7d7a53c1a /import/src/main.rs | |
parent | 3ed621256f1e02032250477fa574eab38bd34976 (diff) | |
download | weareserver-dc8304afefa71037bea99722bee29f7645753836.tar weareserver-dc8304afefa71037bea99722bee29f7645753836.tar.bz2 weareserver-dc8304afefa71037bea99722bee29f7645753836.tar.zst |
rename crates and binaries
Diffstat (limited to 'import/src/main.rs')
-rw-r--r-- | import/src/main.rs | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/import/src/main.rs b/import/src/main.rs new file mode 100644 index 0000000..554a2bd --- /dev/null +++ b/import/src/main.rs @@ -0,0 +1,304 @@ +/* + wearechat - generic multiplayer game with voip + Copyright (C) 2025 metamuffin + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published by + the Free Software Foundation, version 3 of the License only. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see <https://www.gnu.org/licenses/>. +*/ +#![feature(iter_array_chunks)] +#![allow(clippy::too_many_arguments, clippy::type_complexity)] +pub mod animation; +pub mod mesh; +pub mod physics; +pub mod prefab; +pub mod vrm; + +use anyhow::{Result, bail}; +use clap::Parser; +use gltf::{image::Source, scene::Transform}; +use humansize::BINARY; +use image::{ImageReader, codecs::webp::WebPEncoder}; +use log::{debug, info}; +use prefab::import_prefab; +use rand::random; +use std::{ + borrow::Cow, + collections::HashMap, + fs::File, + io::{BufWriter, Cursor, Read, Write}, + marker::PhantomData, + net::{SocketAddr, TcpStream}, + path::{Path, PathBuf}, + sync::{Arc, Mutex}, + thread::{self, sleep}, + time::Duration, +}; +use weareshared::{ + Affine3A, Vec3A, + helper::ReadWrite, + packets::{Data, Object, Packet, Resource}, + resources::{Image, RespackEntry}, + respack::save_respack, + store::ResourceStore, + vec3a, +}; + +#[derive(Parser)] +pub struct Args { + #[arg(short, long)] + address: Option<SocketAddr>, + + /// Output converted prefab as resource package + #[arg(short = 'o', long)] + pack: Option<PathBuf>, + + /// Path(s) to a glTF file, binary or json format + scene: Vec<PathBuf>, + /// Send all resources to the server then quit + #[arg(short, long)] + push: bool, + + /// Remove all other object from the world + #[arg(short, long)] + clear: bool, + /// Add the object to the world + #[arg(short, long)] + add: bool, + /// Transcode all textures to WebP + #[arg(short, long)] + webp: bool, + /// Add skybox + #[arg(long)] + skybox: Option<PathBuf>, + /// Override prefab name + #[arg(short, long)] + name: Option<String>, + #[arg(short, long)] + scale: Option<f32>, + #[arg(short, long)] + dry_run: bool, + #[arg(short, long)] + line_up: bool, + + #[arg(long)] + use_cache: bool, + + #[arg(short = 'S', long)] + with_default_sun: bool, + + #[arg(long)] + animation: Option<PathBuf>, + #[arg(long)] + animation_bone_map: Option<PathBuf>, + #[arg(long)] + animation_rotation_y: Option<f32>, + #[arg(long)] + animation_scale: Option<f32>, + #[arg(long)] + animation_apply_ibm: bool, + + /// Spins the object + #[arg(long)] + debug_spin: bool, + /// Adds a light + #[arg(long)] + debug_light: bool, + #[arg(long)] + no_particles: bool, + #[arg(long)] + no_animations: bool, +} + +fn main() -> Result<()> { + env_logger::init_from_env("LOG"); + let args = Args::parse(); + + let store = if args.use_cache && !args.pack.is_some() { + ResourceStore::new_env()? + } else { + ResourceStore::new_memory() + }; + + let mut prefabs = Vec::new(); + let texture_cache = Arc::new(Mutex::new(HashMap::new())); + + for scenepath in &args.scene { + prefabs.push(import_prefab(&store, &texture_cache, scenepath, &args)?); + } + + let mut size = 0; + store.iter(|_k, len| size += len).unwrap(); + info!( + "prefab has network size of {}", + humansize::format_size(size, BINARY) + ); + + if args.dry_run { + return Ok(()); + } + if let Some(outpath) = args.pack { + let entry = store.set(&RespackEntry { name: None })?; + let mut resources = Vec::new(); + store.iter(|r, _| resources.push(r))?; + save_respack( + BufWriter::new(File::create(outpath)?), + &store, + &resources, + Some(entry), + )?; + } else if let Some(address) = args.address { + let mut sock = TcpStream::connect(address)?; + Packet::Connect(random()).write(&mut sock)?; + for p in &prefabs { + Packet::AnnouncePrefab(p.clone()).write(&mut sock)?; + } + sock.flush()?; + + let mut obs = Vec::new(); + if args.add { + for (i, p) in prefabs.iter().enumerate() { + let ob = Object::new(); + info!("adding object {ob}"); + Packet::Add(ob, p.clone()).write(&mut sock)?; + if args.line_up { + Packet::Position(ob, vec3a(i as f32 * 1.2, 0., i as f32 * 0.3), Vec3A::ZERO) + .write(&mut sock)?; + } + obs.push(ob); + } + sock.flush()?; + } + + if args.debug_spin { + let ob = obs[0]; + let mut sock2 = sock.try_clone().unwrap(); + thread::spawn(move || { + let mut x = 0.; + loop { + Packet::Position(ob, Vec3A::ZERO, vec3a(0., x * 0.1, 0.)) + .write(&mut sock2) + .unwrap(); + sock2.flush().unwrap(); + x += 0.1; + sleep(Duration::from_millis(50)); + } + }); + } + if args.push { + if args.use_cache { + return Ok(()); + } + store.iter(|k, _| { + Packet::RespondResource(k, Data(store.get_raw(k).unwrap().unwrap())) + .write(&mut sock) + .unwrap(); + })?; + sock.flush()?; + } else { + loop { + let packet = Packet::read(&mut sock)?; + match packet { + Packet::RequestResource(hash) => { + if let Some(d) = store.get_raw(hash)? { + Packet::RespondResource(hash, Data(d)).write(&mut sock)?; + sock.flush()?; + } + } + Packet::Add(ob_a, _) => { + if args.clear && !obs.contains(&ob_a) { + info!("removing object {ob_a}"); + Packet::Remove(ob_a).write(&mut sock)?; + sock.flush()?; + } + } + _ => (), + } + } + } + } else { + bail!("no output option specified. either provide an address to a server or use --pack") + } + Ok(()) +} + +pub type TextureCache = Arc<Mutex<HashMap<String, Resource<Image<'static>>>>>; +fn load_texture( + name: &str, + store: &ResourceStore, + path: &Path, + buffers: &[gltf::buffer::Data], + source: &Source, + webp: bool, + texture_cache: &TextureCache, +) -> Result<Resource<Image<'static>>> { + let (mut image, uri) = match source { + gltf::image::Source::View { view, mime_type } => { + debug!("{name} texture is embedded and of type {mime_type:?}"); + let buf = + &buffers[view.buffer().index()].0[view.offset()..view.offset() + view.length()]; + (Image(Cow::Borrowed(buf)), None) + } + gltf::image::Source::Uri { + uri, + mime_type: Some(mime_type), + } => { + debug!("{name} texture is {uri:?} and of type {mime_type:?}"); + if let Some(res) = texture_cache.lock().unwrap().get(*uri) { + return Ok(res.to_owned()); + } + let path = path.join(uri); + let mut buf = Vec::new(); + File::open(path)?.read_to_end(&mut buf)?; + (Image(buf.into()), Some(uri.to_string())) + } + gltf::image::Source::Uri { + uri, + mime_type: None, + } => { + debug!("{name} texture is {uri:?} and has no type"); + if let Some(res) = texture_cache.lock().unwrap().get(*uri) { + return Ok(res.to_owned()); + } + let path = path.join(uri); + let mut buf = Vec::new(); + File::open(path)?.read_to_end(&mut buf)?; + (Image(buf.into()), Some(uri.to_string())) + } + }; + + if webp { + let mut image_out = Vec::new(); + + let len = image.0.len(); + ImageReader::new(Cursor::new(image.0)) + .with_guessed_format()? + .decode()? + .write_with_encoder(WebPEncoder::new_lossless(&mut image_out))?; + debug!("webp encode: {len} -> {}", image_out.len()); + image = Image(Cow::Owned(image_out)); + } + let res = Resource(store.set(&image)?.0, PhantomData); + if let Some(uri) = uri { + texture_cache.lock().unwrap().insert(uri, res.clone()); + } + Ok(res) +} + +pub fn transform_to_affine(trans: Transform) -> Affine3A { + let mat = trans.matrix(); + Affine3A::from_cols_array_2d(&[ + [mat[0][0], mat[0][1], mat[0][2]], + [mat[1][0], mat[1][1], mat[1][2]], + [mat[2][0], mat[2][1], mat[2][2]], + [mat[3][0], mat[3][1], mat[3][2]], + ]) +} |