/* 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 . */ #![feature(iter_array_chunks)] pub mod mesh; use anyhow::{Result, bail}; use clap::Parser; use gltf::image::Source; use log::info; use mesh::import_mesh; use rand::random; use std::{ fs::File, io::{Read, Write}, net::{SocketAddr, TcpStream}, path::{Path, PathBuf}, thread::{self, sleep}, time::Duration, }; use weareshared::{ Vec3A, packets::{Data, Object, Packet, ReadWrite, Resource}, resources::{Image, LightPart, Prefab}, store::ResourceStore, vec3a, }; #[derive(Parser)] struct Args { address: SocketAddr, scene: PathBuf, #[arg(short, long)] push: bool, #[arg(short, long)] spin: bool, #[arg(short, long)] clear: bool, #[arg(short, long)] name: Option, } fn main() -> Result<()> { env_logger::init_from_env("LOG"); let args = Args::parse(); let mut sock = TcpStream::connect(args.address)?; let store = ResourceStore::new_memory(); Packet::Connect(random()).write(&mut sock)?; let (gltf, buffers, _) = gltf::import(&args.scene)?; let path_base = args.scene.parent().unwrap(); let mut prefab = Prefab::default(); for node in gltf.nodes() { if let Some(mesh) = node.mesh() { import_mesh(mesh, &buffers, &store, path_base, &node, &mut prefab)?; } let (position, _, _) = node.transform().decomposed(); if let Some(light) = node.light() { let emission = Some(Vec3A::from_array(light.color()) * light.intensity()); prefab.light.push(( Vec3A::from_array(position), store.set(&LightPart { emission, ..Default::default() })?, )); } } prefab.light.push(( vec3a(5., 5., 5.), store.set(&LightPart { emission: Some(vec3a(0.5, 0.1, 1.0)), radius: Some(0.2), })?, )); let ob = Object::new(); let pres = store.set(&prefab)?; Packet::Add(ob, pres.clone()).write(&mut sock)?; sock.flush()?; if args.spin { let mut sock2 = sock.try_clone().unwrap(); thread::spawn(move || { let mut x = 0.; loop { Packet::Position(ob, Vec3A::ZERO, vec3a(x, x * 0.3, x * 0.1)) .write(&mut sock2) .unwrap(); sock2.flush().unwrap(); x += 0.1; sleep(Duration::from_millis(50)); } }); } if let Some(name) = args.name { Packet::PrefabName(pres, name).write(&mut sock)?; sock.flush()?; } if args.push { store.iter(|d| { Packet::RespondResource(Data(d.to_vec())) .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(Data(d)).write(&mut sock)?; sock.flush()?; } } Packet::Add(ob_a, _) => { if ob_a != ob { info!("removing object {ob_a}"); Packet::Remove(ob_a).write(&mut sock)?; sock.flush()?; } } _ => (), } } } Ok(()) } fn load_texture( name: &str, store: &ResourceStore, path: &Path, buffers: &[gltf::buffer::Data], source: &Source, ) -> Result> { match source { gltf::image::Source::View { view, mime_type } => { info!("{name} texture is embedded and of type {mime_type:?}"); let buf = &buffers[view.buffer().index()].0[view.offset()..view.offset() + view.length()]; return Ok(store.set(&Image(buf.to_vec()))?); } gltf::image::Source::Uri { uri, mime_type: Some(mime_type), } => { info!("{name} texture is at {uri:?} and of type {mime_type:?}"); let path = path.join(uri); let mut buf = Vec::new(); File::open(path)?.read_to_end(&mut buf)?; return Ok(store.set(&Image(buf))?); } _ => { bail!("texture is external and has no mime type") } } }