use std::ops::Deref;
use std::sync::Arc;
use crate::{media::CacheManager, vulkan::RectF32};
use perovskite_core::protocol::entities as proto;
use perovskite_core::protocol::render::TextureTransform;
use rustc_hash::{FxHashMap, FxHashSet};
use texture_packer::Rect;
use super::{VkAllocator, VulkanContext};
use crate::media::load_or_generate_image;
use crate::vulkan::atlas::{TextureAtlas, TextureKey};
use crate::vulkan::shaders::entity_geometry::EntityVertex;
use crate::vulkan::shaders::VkDrawBufferGpu;
use anyhow::{bail, ensure, Result};
use cgmath::{Matrix3, Rad, Vector3, Zero};
pub(crate) struct EntityRenderer {
texture_atlas: TextureAtlas,
_allocator: Arc<VkAllocator>,
mesh_definitions: FxHashMap<u32, EntityMesh>,
singleton_gpu_buffers: FxHashMap<u32, Option<VkDrawBufferGpu<EntityVertex>>>,
}
impl EntityRenderer {
pub(crate) async fn new(
entity_defs: Vec<proto::EntityDef>,
cache_manager: &mut CacheManager,
ctx: &VulkanContext,
) -> Result<EntityRenderer> {
let mut all_texture_names = FxHashSet::default();
let mut pack_textures = FxHashSet::default();
for def in &entity_defs {
if let Some(appearance) = &def.appearance {
for mesh in &appearance.custom_mesh {
if let Some(tex) = &mesh.texture {
pack_textures.insert(TextureKey::from(tex));
all_texture_names.insert(tex.diffuse.clone());
if !tex.rt_specular.is_empty() {
all_texture_names.insert(tex.rt_specular.clone());
}
if !tex.emissive.is_empty() {
all_texture_names.insert(tex.emissive.clone());
}
if !tex.normal_map.is_empty() {
all_texture_names.insert(tex.normal_map.clone());
}
if !tex.alt_diffuse.is_empty() {
all_texture_names.insert(tex.alt_diffuse.clone());
}
}
}
}
}
let mut fetched_textures = FxHashMap::default();
for x in all_texture_names {
let texture = load_or_generate_image(cache_manager, &x).await?;
fetched_textures.insert(x, texture);
}
let texture_atlas = TextureAtlas::new(&ctx, pack_textures, fetched_textures)?;
let mut all_meshes = FxHashMap::default();
let mut singleton_gpu_buffers = FxHashMap::default();
for def in entity_defs {
if let Some(appearance) = def.appearance {
let mesh = EntityRenderer::pre_render(
appearance,
&texture_atlas.texel_coords,
(texture_atlas.width, texture_atlas.height),
def.short_name,
)?;
let singleton_buffer =
VkDrawBufferGpu::from_buffers(&mesh.vtx, &mesh.idx, ctx.clone_allocator())?;
singleton_gpu_buffers.insert(def.entity_class, singleton_buffer);
all_meshes.insert(def.entity_class, mesh);
}
}
Ok(EntityRenderer {
texture_atlas,
_allocator: ctx.clone_allocator(),
mesh_definitions: all_meshes,
singleton_gpu_buffers,
})
}
pub(crate) fn atlas(&self) -> &TextureAtlas {
&self.texture_atlas
}
fn pre_render(
appearance: proto::EntityAppearance,
texture_coords: &FxHashMap<TextureKey, Rect>,
atlas_dims: (u32, u32),
name: String,
) -> Result<EntityMesh> {
let meshes = &appearance.custom_mesh;
let vertex_count = meshes.iter().map(|x| x.x.len()).sum();
if vertex_count >= u32::MAX as usize {
bail!("Too many vertices");
}
let mut vertices = Vec::with_capacity(vertex_count);
let mut indices = Vec::with_capacity(meshes.iter().map(|x| x.indices.len()).sum());
let mut aabb_min = Vector3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY);
let mut aabb_max = Vector3::new(-f32::INFINITY, -f32::INFINITY, -f32::INFINITY);
for mesh in meshes {
let tex_ref = mesh.texture.as_ref();
let tex_rectangle: RectF32 = tex_ref
.and_then(|x| texture_coords.get(&TextureKey::from(x)))
.unwrap_or_else(|| texture_coords.get(&TextureKey::FallbackUnknownTex).unwrap())
.into();
let transform = tex_ref
.map(|t| {
TextureTransform::try_from(t.texture_transform)
.unwrap_or(TextureTransform::None)
})
.unwrap_or(TextureTransform::None);
if transform != TextureTransform::None {
if let Some(t) = tex_ref {
if !t.normal_map.is_empty() {
log::warn!(
"Texture \"{}\" has a normal map and a texture transform; the normals \
in the normal map will NOT be automatically adjusted to match the new \
orientation",
t.diffuse
);
}
}
}
let tex_rectangle = tex_rectangle.with_transform(transform).div(atlas_dims);
let vertices_len = mesh.x.len();
ensure!(mesh.y.len() == vertices_len);
ensure!(mesh.z.len() == vertices_len);
ensure!(mesh.u.len() == vertices_len);
ensure!(mesh.v.len() == vertices_len);
ensure!(mesh.nx.len() == vertices_len);
ensure!(mesh.ny.len() == vertices_len);
ensure!(mesh.nz.len() == vertices_len);
for i in 0..vertices_len {
let [tl_u, tl_v] = tex_rectangle.tl();
let [tr_u, tr_v] = tex_rectangle.tr();
let [bl_u, bl_v] = tex_rectangle.bl();
let mu = mesh.u[i];
let mv = mesh.v[i];
let u = tl_u + mu * (tr_u - tl_u) + mv * (bl_u - tl_u);
let v = tl_v + mu * (tr_v - tl_v) + mv * (bl_v - tl_v);
vertices.push(EntityVertex {
position: [mesh.x[i], mesh.y[i], mesh.z[i]],
normal: [mesh.nx[i], mesh.ny[i], mesh.nz[i]],
uv_texcoord: [u, v],
});
aabb_min.x = aabb_min.x.min(mesh.x[i]);
aabb_min.y = aabb_min.y.min(mesh.y[i]);
aabb_min.z = aabb_min.z.min(mesh.z[i]);
aabb_max.x = aabb_max.x.max(mesh.x[i]);
aabb_max.y = aabb_max.y.max(mesh.y[i]);
aabb_max.z = aabb_max.z.max(mesh.z[i]);
}
ensure!(mesh.indices.len() % 3 == 0);
ensure!(mesh.indices.iter().all(|x| *x < vertices_len as u32));
let offset = indices.len();
for index in &mesh.indices {
indices.push(index + offset as u32);
}
}
Ok(EntityMesh {
vtx: vertices,
idx: indices,
attach_offset: match &appearance.attachment_offset {
Some(x) => x.try_into().unwrap(),
None => Vector3::zero(),
},
attach_in_model_space: appearance.attachment_offset_in_model_space,
aabb: (aabb_min, aabb_max),
def: appearance.clone(),
name,
})
}
pub(crate) fn transform_position(
&self,
class: u32,
pos: Vector3<f64>,
face_dir: Rad<f32>,
pitch: Rad<f32>,
) -> Vector3<f64> {
let def = match self.mesh_definitions.get(&class) {
Some(x) => x,
None => return pos,
};
if def.attach_in_model_space {
let rotation: Matrix3<f64> = (Matrix3::from_angle_y(face_dir)
* Matrix3::from_angle_x(pitch))
.cast()
.unwrap();
pos + (rotation * def.attach_offset)
} else {
def.attach_offset + pos
}
}
pub(crate) fn get_singleton(&self, class: u32) -> Option<VkDrawBufferGpu<EntityVertex>> {
self.singleton_gpu_buffers.get(&class).cloned().flatten()
}
pub(crate) fn client_info(&self, class: u32) -> Option<&proto::EntityAppearance> {
self.mesh_definitions.get(&class).map(|x| &x.def)
}
pub(crate) fn class_name(&self, class: u32) -> Option<&str> {
self.mesh_definitions.get(&class).map(|x| x.name.deref())
}
pub(crate) fn mesh_aabb(&self, class: u32) -> Option<(Vector3<f32>, Vector3<f32>)> {
self.mesh_definitions.get(&class).map(|x| x.aabb)
}
}
pub(crate) struct EntityMesh {
pub(crate) vtx: Vec<EntityVertex>,
pub(crate) idx: Vec<u32>,
pub(crate) attach_offset: Vector3<f64>,
pub(crate) attach_in_model_space: bool,
pub(crate) aabb: (Vector3<f32>, Vector3<f32>),
pub(crate) name: String,
pub(crate) def: proto::EntityAppearance,
}