crystal-engine 0.2.1

A simple 3D engine
Documentation
use super::{loader::SourceOrShape, Model, ModelGroup, ModelHandle};
use crate::GameState;
use cgmath::{Euler, Rad, Vector3, Zero};
use parking_lot::RwLock;
use std::sync::Arc;
use vulkano::{
    buffer::{BufferUsage, CpuAccessibleBuffer},
    command_buffer::{AutoCommandBuffer, CommandBufferExecFuture},
    device::Queue,
    format::R8G8B8A8Srgb,
    image::{Dimensions, ImmutableImage},
    sync::NowFuture,
};

pub struct ModelBuilder<'a> {
    game_state: &'a mut GameState,
    source_or_shape: SourceOrShape<'a>,
    fallback_color: Option<Vector3<f32>>,
    texture: Option<&'a str>,
    position: Vector3<f32>,
    rotation: Euler<Rad<f32>>,
    scale: f32,
}

impl<'a> ModelBuilder<'a> {
    pub(crate) fn new(game_state: &'a mut GameState, source_or_shape: SourceOrShape<'a>) -> Self {
        Self {
            game_state,
            source_or_shape,
            fallback_color: None,
            texture: None,
            position: Vector3::zero(),
            rotation: Euler::new(Rad(0.0), Rad(0.0), Rad(0.0)),
            scale: 1.0,
        }
    }

    pub fn with_fallback_color(mut self, color: impl Into<Vector3<f32>>) -> Self {
        self.fallback_color = Some(color.into());
        self
    }
    pub fn with_texture_from_file(mut self, texture_src: &'a str) -> Self {
        self.texture = Some(texture_src);
        self
    }
    pub fn with_position(mut self, position: impl Into<Vector3<f32>>) -> Self {
        self.position = position.into();
        self
    }
    pub fn with_rotation(mut self, rotation: Euler<Rad<f32>>) -> Self {
        self.rotation = rotation;
        self
    }
    pub fn with_scale(mut self, scale: f32) -> Self {
        self.scale = scale;
        self
    }

    pub fn build(self) -> ModelHandle {
        let position = self.position;
        let rotation = self.rotation;
        let scale = self.scale;

        let source = self.source_or_shape.parse();
        let device = self.game_state.device.clone();
        let queue = self.game_state.queue.clone();

        let (tex, mut futures) = if let Some(texture) = self.texture {
            let (tex, tex_future) = load_texture(self.game_state.queue.clone(), texture);
            (Some(tex), vec![Box::new(tex_future) as _])
        } else {
            (None, Vec::new())
        };

        let vertex_buffer = if let Some(vertices) = source.vertices {
            CpuAccessibleBuffer::from_iter(
                device.clone(),
                BufferUsage::all(),
                false,
                vertices.iter().copied(),
            )
            .ok()
        } else {
            None
        };

        let mut groups: Vec<_> = source
            .parts
            .into_iter()
            .map(|part| {
                let (group, maybe_future) =
                    ModelGroup::from_part(device.clone(), queue.clone(), &tex, part);
                if let Some(fut) = maybe_future {
                    futures.push(fut);
                }
                group
            })
            .collect();

        if groups.is_empty() {
            // we always need a single group, so add a dummy group
            // TODO: Why do we always need a single group?
            groups.push(ModelGroup::from_tex(tex));
        }

        let model = Model {
            vertex_buffer,
            groups,
            texture_future: RwLock::new(futures),
        };
        if cfg!(debug_assertions)
            && model.vertex_buffer.is_none()
            && model.groups.iter().all(|g| g.vertex_buffer.is_none())
        {
            panic!(
                "Model {:?} has no valid vertex buffer",
                self.source_or_shape
            );
        }

        let (handle, id, data) =
            ModelHandle::from_model(Arc::new(model), self.game_state.model_handle_sender.clone());
        self.game_state.model_handles.insert(id, data);

        // TODO: Immediately set this on the handle
        handle.modify(|data| {
            data.position = position;
            data.rotation = rotation;
            data.scale = scale;
        });

        handle
    }
}

fn load_texture(
    queue: Arc<Queue>,
    path: &str,
) -> (
    Arc<ImmutableImage<R8G8B8A8Srgb>>,
    CommandBufferExecFuture<NowFuture, AutoCommandBuffer>,
) {
    let image = image::open(path).unwrap().to_rgba();
    let dimensions = Dimensions::Dim2d {
        width: image.width(),
        height: image.height(),
    };

    ImmutableImage::from_iter(
        image.into_raw().into_iter(),
        dimensions,
        R8G8B8A8Srgb,
        queue,
    )
    .unwrap()
}