Skip to main content

flow_ngin/data_structures/
block.rs

1//! Building blocks implemented via GPU instancing.
2//!
3//! Provides [`BuildingBlocks`], a collection of identically-shaped objects
4//! (e.g., construction blocks or crowds) rendered efficiently using GPU instancing. Note that
5//! hidden blocks are not culled, so this may not be optimal for large voxel worlds.
6
7use crate::{
8    context::{Context, GPUResource}, data_structures::{
9        instance::Instance,
10        model::{self},
11    }, pick::PickId, render::{Instanced, Render}, resources::{self, pick::load_pick_model}
12};
13use cgmath::{One, Rotation3, Zero};
14use wgpu::{Device, util::DeviceExt};
15
16/// A collection of identically-shaped building blocks.
17///
18/// Uses GPU instancing to efficiently render many copies of the same model
19/// with different transformations. Currently does not perform frustum culling
20/// or occlusion culling, so performance may degrade with very large numbers of blocks.
21pub struct BuildingBlocks {
22    // TODO: create apis and make fields private
23    pub id: PickId,
24    pub obj_model: model::Model,
25    // TODO: retire this param
26    #[allow(dead_code)]
27    obj_file: String,
28    instances: Vec<Instance>,
29    instance_buffer: wgpu::Buffer,
30    buffer_size_needs_change: bool,
31}
32
33impl AsRef<BuildingBlocks> for BuildingBlocks {
34    fn as_ref(&self) -> &BuildingBlocks {
35        self
36    }
37}
38
39impl BuildingBlocks {
40    pub async fn new(
41        id: impl Into<PickId>,
42        queue: &wgpu::Queue,
43        device: &wgpu::Device,
44        start_position: cgmath::Vector3<f32>,
45        start_rotation: cgmath::Quaternion<f32>,
46        amount: usize,
47        obj_file: &str,
48    ) -> Self {
49        let obj_model = resources::load_model_obj(obj_file, &device, &queue).await;
50        if let Err(e) = obj_model {
51            panic!("Error failed to load model: {}", e);
52        }
53        let obj_model = obj_model.unwrap();
54
55        let instances = (0..amount)
56            .map(|_| {
57                let mut instance = Instance::new();
58                instance.position = start_position;
59                let rotation = if start_position.is_zero() {
60                    cgmath::Quaternion::from_axis_angle(cgmath::Vector3::unit_z(), cgmath::Deg(0.0))
61                } else {
62                    start_rotation
63                };
64                instance.rotation = rotation;
65                instance
66            })
67            .collect::<Vec<_>>();
68
69        let instance_data = instances.iter().map(Instance::to_raw).collect::<Vec<_>>();
70        let instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
71            label: Some("Instance Buffer"),
72            contents: bytemuck::cast_slice(&instance_data),
73            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
74        });
75
76        Self {
77            obj_model,
78            instances,
79            obj_file: obj_file.to_string(),
80            instance_buffer,
81            // Ids may be used later for picking, hitboxes, etc.
82            id: id.into(),
83            buffer_size_needs_change: false,
84        }
85    }
86
87    /// Returns an immutable reference to instances
88    pub fn instances(&self) -> &Vec<Instance> {
89        &self.instances
90    }
91
92    /// Returns a mutable reference to instances, assumes the instance size changes and recalculates buffers.
93    /// If you only mutate some values but don't intend to change buffer sizes use `instances_mut_size_unchanged`
94    pub fn instances_mut(&mut self) -> &mut Vec<Instance> {
95        self.buffer_size_needs_change = true;
96        &mut self.instances
97    }
98
99    pub fn instances_mut_size_unchanged(&mut self) -> &mut [Instance] {
100        self.instances.as_mut_slice()
101    }
102
103    pub fn add_instance(&mut self, instance: Instance) {
104        self.instances.push(instance);
105        self.buffer_size_needs_change = true;
106    }
107
108    pub fn add_instances(&mut self, mut instances: Vec<Instance>) {
109        self.instances.append(&mut instances);
110        self.buffer_size_needs_change = true;
111    }
112
113    /**
114     * This constructor creates `amount` instances all located at (0.0, 0.0, 0.0).
115     *
116     * TODO: pass iter fn to choose the transformation
117     */
118    pub async fn mk_multiple(
119        queue: &wgpu::Queue,
120        device: &wgpu::Device,
121        amount: usize,
122        descr: &[(PickId, &'static str)],
123    ) -> Vec<BuildingBlocks> {
124        let futures = descr.into_iter().map(|(id, file_name)| {
125            BuildingBlocks::new(
126                *id,
127                queue,
128                device,
129                cgmath::Vector3::zero(),
130                cgmath::Quaternion::one(),
131                amount,
132                file_name,
133            )
134        });
135        futures::future::join_all(futures).await
136    }
137
138    /**
139     * This method creates a copy of the original Block (and instances) where only the
140     * fragment shader differs. The fragment shader is a U32 id referring to the object
141     * that was drawn.
142     *
143     * This is used to draw a pick shader which allows identifying objects clicked on
144     * with a mouse pointer.
145     *
146     * TODO: make this a trait if possible
147     */
148    pub fn to_clickable(&self, device: &Device, id: PickId) -> Self {
149        let obj_model = load_pick_model(device, id, self.obj_model.meshes.clone()).unwrap();
150
151        let instance_data = self
152            .instances
153            .iter()
154            .map(Instance::to_raw)
155            .collect::<Vec<_>>();
156        let instance_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
157            label: Some("Instance Buffer for Picking"),
158            contents: bytemuck::cast_slice(&instance_data),
159            usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
160        });
161
162        Self {
163            obj_model: obj_model,
164            obj_file: self.obj_file.clone(),
165            instances: self.instances.clone(),
166            instance_buffer,
167            id,
168            buffer_size_needs_change: false,
169        }
170    }
171
172    pub fn clear_first(&mut self, amount: usize, ctx: &Context) {
173        self.buffer_size_needs_change = true;
174        self.instances.drain(0..amount);
175        self.write_to_buffer(&ctx.queue, &ctx.device);
176    }
177
178    pub fn clear_at(&mut self, from: usize, to: usize, ctx: &Context) {
179        self.buffer_size_needs_change = true;
180        self.instances.drain(from..to);
181        self.write_to_buffer(&ctx.queue, &ctx.device);
182    }
183    
184    /// Returns the inner instanced of the `Default` render for possible optimizations with `Defaults`
185    pub fn to_instanced(&self) -> Instanced<'_> {
186        Instanced {
187            instance: &self.instance_buffer,
188            model: &self.obj_model,
189            amount: self.instances.len(),
190            front_face: wgpu::FrontFace::Ccw,
191            id: self.id,
192        }
193    }
194}
195
196impl<'a, 'pass> GPUResource<'a, 'pass> for BuildingBlocks {
197    fn write_to_buffer(&mut self, queue: &wgpu::Queue, device: &wgpu::Device) {
198        let raws = self
199            .instances
200            .iter()
201            .map(Instance::to_raw)
202            .collect::<Vec<_>>();
203        if self.buffer_size_needs_change {
204            self.instance_buffer =
205                device
206                    .create_buffer_init(&wgpu::util::BufferInitDescriptor {
207                        label: Some("Instance Buffer"),
208                        contents: bytemuck::cast_slice(&raws),
209                        usage: wgpu::BufferUsages::VERTEX | wgpu::BufferUsages::COPY_DST,
210                    });
211            self.buffer_size_needs_change = false;
212        } else {
213            queue
214                .write_buffer(&self.instance_buffer, 0, bytemuck::cast_slice(&raws));
215        }
216    }
217
218    fn get_render(&'a self) -> Render<'a, 'pass> {
219        Render::Default(self.to_instanced())
220    }
221}