oxygengine-composite-renderer 0.30.0

Composite renderer module for Oxygengine
Documentation
use crate::{
    component::{CompositeMesh, CompositeRenderable, CompositeSurfaceCache},
    composite_renderer::{Command, Mask, PathElement, Renderable, Triangles},
    math::Vec2,
    mesh_asset_protocol::{Mesh, MeshAsset, MeshVertex},
};
use core::{
    assets::{asset::AssetId, database::AssetsDatabase},
    ecs::{Comp, Universe, WorldRef},
};
use std::collections::HashMap;

#[derive(Debug, Default)]
pub struct CompositeMeshSystemCache {
    meshes_cache: HashMap<String, Mesh>,
    meshes_table: HashMap<AssetId, String>,
}

pub type CompositeMeshSystemResources<'a> = (
    WorldRef,
    &'a AssetsDatabase,
    &'a mut CompositeMeshSystemCache,
    Comp<&'a mut CompositeMesh>,
    Comp<&'a mut CompositeRenderable>,
    Comp<&'a mut CompositeSurfaceCache>,
);

pub fn composite_mesh_system(universe: &mut Universe) {
    let (world, assets, mut cache, ..) = universe.query_resources::<CompositeMeshSystemResources>();

    for id in assets.lately_loaded_protocol("mesh") {
        let id = *id;
        let asset = assets
            .asset_by_id(id)
            .expect("trying to use not loaded mesh asset");
        let path = asset.path().to_owned();
        let asset = asset
            .get::<MeshAsset>()
            .expect("trying to use non-mesh asset");
        let mesh = asset.mesh().clone();
        cache.meshes_cache.insert(path.clone(), mesh);
        cache.meshes_table.insert(id, path);
    }
    for id in assets.lately_unloaded_protocol("mesh") {
        if let Some(path) = cache.meshes_table.remove(id) {
            cache.meshes_cache.remove(&path);
        }
    }

    for (_, (mesh, renderable, surface)) in world
        .query::<(
            &mut CompositeMesh,
            &mut CompositeRenderable,
            Option<&mut CompositeSurfaceCache>,
        )>()
        .iter()
    {
        if mesh.dirty_mesh || mesh.dirty_visuals {
            if let Some(r) = build_renderable(mesh, &cache.meshes_cache) {
                renderable.0 = r;
                if let Some(surface) = surface {
                    surface.rebuild();
                }
                mesh.dirty_mesh = false;
                mesh.dirty_visuals = false;
            }
        }
    }
}

fn build_renderable<'a>(
    mesh: &mut CompositeMesh,
    meshes: &HashMap<String, Mesh>,
) -> Option<Renderable<'a>> {
    if let Some(asset) = meshes.get(mesh.mesh()) {
        if mesh.dirty_mesh {
            if let Some(root) = &asset.rig {
                mesh.setup_bones_from_rig(root);
            }
        }
        if mesh.dirty_visuals {
            let vertices = if let Some(root) = &asset.rig {
                mesh.rebuild_model_space(root);
                build_skined_vertices(&asset.vertices, mesh)
            } else {
                build_vertices(&asset.vertices)
            };
            let masks = asset
                .masks
                .iter()
                .map(|indices| build_mask(&vertices, &indices.indices))
                .collect::<Vec<_>>();
            let mut meta = asset
                .submeshes
                .iter()
                .zip(mesh.materials().iter())
                .filter_map(|(submesh, material)| {
                    if material.alpha > 0.0 {
                        let triangles = Triangles {
                            image: material.image.to_string().into(),
                            color: Default::default(),
                            vertices: vertices.to_vec(),
                            faces: submesh.cached_faces().to_vec(),
                        };
                        let masks = submesh
                            .masks
                            .iter()
                            .map(|i| masks[*i].to_vec())
                            .collect::<Vec<_>>();
                        Some((triangles, material.alpha, material.order, masks))
                    } else {
                        None
                    }
                })
                .collect::<Vec<_>>();
            meta.sort_by(|a, b| a.2.partial_cmp(&b.2).unwrap());
            let count = meta.len() * 4 + meta.iter().fold(0, |a, v| a + v.3.len());
            let mut commands = Vec::with_capacity(count);
            for (triangles, alpha, _, masks) in meta {
                commands.push(Command::Store);
                for mask in masks {
                    let mask = Mask { elements: mask };
                    commands.push(Command::Draw(mask.into()));
                }
                commands.push(Command::Alpha(alpha));
                commands.push(Command::Draw(triangles.into()));
                commands.push(Command::Restore);
            }
            return Some(Renderable::Commands(commands));
        }
    }
    None
}

fn build_skined_vertices(vertices: &[MeshVertex], mesh: &CompositeMesh) -> Vec<(Vec2, Vec2)> {
    vertices
        .iter()
        .map(|v| {
            let p = v.bone_info.iter().fold(Vec2::default(), |a, i| {
                let p = if let Some(m) = mesh.bones_model_space.get(&i.name) {
                    *m * v.position
                } else {
                    v.position
                };
                a + p * i.weight
            });
            (p, v.tex_coord)
        })
        .collect::<Vec<_>>()
}

fn build_vertices(vertices: &[MeshVertex]) -> Vec<(Vec2, Vec2)> {
    vertices
        .iter()
        .map(|v| (v.position, v.tex_coord))
        .collect::<Vec<_>>()
}

fn build_mask(vertices: &[(Vec2, Vec2)], indices: &[usize]) -> Vec<PathElement> {
    let mut result = Vec::with_capacity(indices.len());
    for index in indices {
        if *index == 0 {
            result.push(PathElement::MoveTo(vertices[*index].0));
        } else {
            result.push(PathElement::LineTo(vertices[*index].0));
        }
    }
    result
}